diff --git a/Dockerfile b/Dockerfile index 86595ea5a2..fa89debcee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,8 @@ FROM nginx:mainline # The safest way to build this image is to use `yarn docker-build` +RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/* + COPY packages/app/dist /usr/share/nginx/html COPY docker/default.conf.template /etc/nginx/conf.d/default.conf.template COPY docker/run.sh /usr/local/bin/run.sh diff --git a/docker/run.sh b/docker/run.sh index 7bc5f5fa39..ac120f6597 100755 --- a/docker/run.sh +++ b/docker/run.sh @@ -1,9 +1,41 @@ #!/usr/bin/env bash +set -Eeuo pipefail + # Run nginx as root sed -i 's/user nginx.*$//' /etc/nginx/nginx.conf # Write selected env vars to nginx config envsubst '$PORT' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf +# Inject runtime config into the client +function inject_config() { + # Read runtime config from env in the same way as the @backstage/config-loader package + local config + config="$(jq -n 'env | + with_entries(select(.key | startswith("APP_CONFIG_")) | .key |= sub("APP_CONFIG_"; "")) | + to_entries | + reduce .[] as $item ( + {}; setpath($item.key | split("_"); $item.value | fromjson) + )')" + + >&2 echo "Runtime app config: $config" + + local main_js + main_js="$(ls /usr/share/nginx/html/main.*.chunk.js)" + echo "Writing runtime config to ${main_js}" + + # escape ' and " twice, for both sed and json + local config_escaped_1 + config_escaped_1="$(echo "$config" | jq -cM . | sed -e 's/[\\"\x27]/\\&/g')" # \x27 = ' + # escape / and & for sed + local config_escaped_2 + config_escaped_2="$(echo "$config_escaped_1" | sed -e 's/[\/&]/\\&/g')" + + # Replace __APP_INJECTED_RUNTIME_CONFIG__ in the main chunk with the runtime config + sed -e "s/__APP_INJECTED_RUNTIME_CONFIG__/$config_escaped_2/" -i "$main_js" +} + +inject_config + exec nginx -g 'daemon off;' diff --git a/docs/FAQ.md b/docs/FAQ.md index f33b4b9dac..5fb131528a 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -1,52 +1,226 @@ # FAQ -## Do I have to write plugins in TypeScript? +## Product FAQ: -No, you can use JavaScript if you prefer. +### Can we call Backstage something different? So that it fits our company better? -We want to keep the Backstage core APIs in TypeScript, but don't force it on individual plugins. +Yes, Backstage is just a platform for building your own developer portal. We +happen to call our internal version Backstage, as well, as a reference to our +music roots. You can call your version whatever suits your team, company, or +brand. -## Q: Why Material-UI? +### Is Backstage a monitoring platform? -The short answer is that it's what we've been using in Backstage internally. +No, but it can be! Backstage is designed to be a developer portal for all your +infrastructure tooling, services, and documentation. So, it's not a monitoring +platform — but that doesn't mean you can't integrate a monitoring tool into +Backstage by writing +[a plugin](https://github.com/spotify/faq#what-is-a-plugin-in-backstage). -The original choice is based on Google's material design being a thorough and well -thought out full design system, with many mature and powerful libraries implementing -both the system itself and auxiliary components that we knew that we would like to use. +### How is Backstage licensed? -It strikes a good balance between power, customisability, and ease of use. Since a core -focus of Backstage is to make plugin developers productive with as few hurdles as -possible, material-ui lets plugin makers both get going easily with well-known tech -and a large flora of components. +Backstage was released as free and open software by Spotify and is licensed +under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). -## Q: Are you planning on having plugins cooked into the repo or should they be developed in separate repos? +### Why did we open source Backstage? -Additional open sourced plugins would be added to the `plugins` directory in this monorepo. +We hope to see Backstage become the infrastructure standard everywhere. When we +saw how much Backstage improved developer experience and productivity +internally, we wanted to share those gains. After all, if Backstage can create +order in an engineering environment as open and diverse as ours, then we're +pretty sure it can create order (and boost productivity) anywhere. To learn +more, read our blog post, +"[What the heck is Backstage anyway?](https://backstage.io/blog/2020/03/18/what-is-backstage)" -While we encourage using the open source model, integrators that want to experiment with -Backstage internally may also choose to develop closed source plugins in a manner that suits -them best, for example in their respective Backstage source repository. +### Will Spotify's internal plugins be open sourced, too? -## Q: Any plans for integrating with other repository managers such as Gitlab or Bitbucket? +Yes, we've already started releasing open source versions of some of the plugins +we use here, and we'll continue to do so. +[Plugins](https://github.com/spotify/faq#what-is-a-plugin-in-backstage) are the +building blocks of functionality in Backstage. We have over 120 plugins inside +Spotify — many of those are specialized for our use, so will remain internal and +proprietary to us. But we estimate that about a third of our existing plugins +make good open source candidates. (And we'll probably end up writing some brand +new ones, too.) ​ -We chose Github by the fact that it is the tool that we are most familiar with and that will naturally -lead to integrations for Github specifically being developed in an early stage. +### What's the roadmap for Backstage? -Hosting this project on Github does not exclude integrations with other alternatives such as Gitlab or -Bitbucket. We believe that in time there will be plugins that will provide functionality for these tools -as well. Hopefully contributed by the community. +​ We envision three phases, which you can learn about in +[our project roadmap](https://github.com/spotify/backstage#project-roadmap). +Even though the open source version of Backstage is relatively new compared to +our internal version, we have already begun work on various aspects of all three +phases. Looking at the +[milestones for active issues](https://github.com/spotify/backstage/milestones) +will also give you a sense of our progress. -And note that implementations of Backstage can be hosted wherever you feel suits your needs best. +### My company doesn't have thousands of developers or services. Is Backstage overkill? -## Q: Can Backstage by used for other things than developer portals? +Not at all! A core reason to adopt Backstage is to standardize how software is +built at your company. It's easier to decide on those standards as a small +company, and grows in importance as the company grows. Backstage sets a +foundation, and an early investment in your infrastructure becomes even more +valuable as you grow. -Yes. +### Our company has a strong design language system/brand that we want to incorporate. Does Backstage support this? -The core frontend framework could be used for building any large-scale web application where multiple teams are building separate parts of the app, but you want the overall experience to be consistent. +Yes! The Backstage UI is built using Material-UI. With the theming capabilities +of Material-UI, you are able to adapt the interface to your brand guidelines. -That being said, in [Phase 2](https://github.com/spotify/backstage#project-roadmap) of the project we will add features that are needed for developer portals and systems for managing software ecosystems. Our ambition will be to keep Backstage modular. +## Technical FAQ: -## Q: My company doesn't have thousands of developers. Is Backstage overkill? +### Why Material-UI? -Not really. Sure, having something like Backstage gets more important as the number of developers in your company grows. One of the core reasons to adopt Backstage is to help standardise how software is built at your company. Setting guidelines and deciding on standards is easier when your company is smaller. +The short answer is that's what we've been using in Backstage internally. +The original decision was based on Google's Material Design being a thorough, +well thought out and complete design system, with many mature and powerful +libraries implemented in both the system itself and auxiliary components that we +knew that we would like to use. + +It strikes a good balance between power, customizability, and ease of use. A +core focus of Backstage is to make plugin developers productive with as few +hurdles as possible. Material-UI lets plugin makers get going easily with both +well-known tech and a large flora of components. ​ + +### What technology does Backstage use? + +​ The code base is a large-scale React application that uses TypeScript. For +[Phase 2](https://github.com/spotify/backstage#project-roadmap), we plan to use +Node.js and GraphQL. ​ + +### What is the end-to-end user flow? The happy path story. + +​ There are three main user profiles for Backstage: the integrator, the +contributor, and the software engineer. ​ The **integrator** hosts the Backstage +app and configures which plugins are available to use in the app. ​ The +**contributor** adds functionality to the app by writing plugins. ​ The +**software engineer** uses the app's functionality and interacts with its +plugins. ​ + +### What is a "plugin" in Backstage? + +​ Plugins are what provide the feature functionality in Backstage. They are used +to integrate different systems into Backstage's frontend, so that the developer +gets a consistent UX, no matter what tool or service is being accessed on the +other side. ​ Each plugin is treated as a self-contained web app and can include +almost any type of content. Plugins all use a common set of platform APIs and +reusable UI components. Plugins can fetch data either from the backend or an API +exposed through the proxy. ​ Learn more about +[the different components](https://github.com/spotify/backstage#overview) that +make up Backstage. ​ + +### Do I have to write plugins in TypeScript? + +​ No, you can use JavaScript if you prefer. ​ We want to keep the Backstage core +APIs in TypeScript, but aren't forcing it on individual plugins. ​ + +### How do I find out if a plugin already exists? + +​ Before you write a plugin, +[search the plugin issues](https://github.com/spotify/backstage/issues?q=is%3Aissue+label%3Aplugin+) +to see if it already exists or is in the works. If no one's thought of it yet, +great! Open a new issue as +[a plugin suggestion](https://github.com/spotify/backstage/issues/new/choose) +and describe what your plugin will do. This will help coordinate our +contributors' efforts and avoid duplicating existing functionality. ​ In the +future, we will create +[a plugin gallery](https://github.com/spotify/backstage/issues/260) where people +can browse and search for all available plugins. ​ + +### Which plugin is used the most at Spotify? + +​ By far, our most-used plugin is our TechDocs plugin, which we use for creating +technical documentation. Our philosophy at Spotify is to treat "docs like code", +where you write documentation using the same workflow as you write your code. +This makes it easier to create, find, and update documentation. We hope to +release +[the open source version](https://github.com/spotify/backstage/issues/687) in +the future. (See also: +"[Will Spotify's internal plugins be open sourced, too?](https://github.com/spotify/faq#will-spotifys-internal-plugins-be-open-sourced-too)" +above) ​ + +### Are you planning to have plugins baked into the repo? Or should they be developed in separate repos? + +​ Contributors can add open source plugins to the plugins directory in +[this monorepo](https://github.com/spotify/backstage). Integrators can then +configure which open source plugins are available to use in their instance of +the app. Open source plugins are downloaded as npm packages published in the +open source repository. ​ While we encourage using the open source model, we +know there are cases where contributors might want to experiment internally or +keep their plugins closed source. Contributors writing closed source plugins +should develop them in the plugins directory in their own Backstage repository. +Integrators also configure closed source plugins locally from the monorepo. ​ + +### Any plans for integrating with other repository managers, such as GitLab or Bitbucket? + +​ We chose GitHub because it is the tool that we are most familiar with, so that +will naturally lead to integrations for GitHub being developed at an early +stage. ​ Hosting this project on GitHub does not exclude integrations with +alternatives, such as GitLab or Bitbucket. We believe that in time there will be +plugins that will provide functionality for these tools as well. Hopefully, +contributed by the community! ​ Also note, implementations of Backstage can be +hosted wherever you feel suits your needs best. ​ + +### Who maintains Backstage? + +​ Spotify will maintain the open source core, but we envision different parts of +the project being maintained by various companies and contributors. We also +envision a large, diverse ecosystem of open source plugins, which would be +maintained by their original authors/contributors or by the community. ​ When it +comes to +[deployment](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md), +the system integrator (typically, the infrastructure team in your organization) +maintains Backstage in your own environment. ​ + +### Does Spotify provide a managed version of Backstage? + +​ No, this is not a service offering. We build the piece of software, and +someone in your infrastructure team is responsible for +[deploying](https://github.com/spotify/backstage/blob/master/DEPLOYMENT.md) and +maintaining it. ​ + +### How secure is Backstage? + +​ We take security seriously. When it comes to packages and code we scan our +repositories periodically and update our packages to the latest versions. When +it comes to deployment of Backstage within an organisation it depends on the +deployment and security setup in your organisation. Reach out to us on +[Discord](https://discord.gg/MUpMjP2) if you have specific queries. + +Please report sensitive security issues via Spotify's +[bug-bounty program](https://hackerone.com/spotify) rather than GitHub. ​ + +### Does Backstage collect any information that is shared with Spotify? + +​ No. Backstage does not collect any telemetry from any third party using the +platform. Spotify, and the open source community, does have access to +[GitHub Insights](https://github.com/features/insights), which contains +information such as contributors, commits, traffic, and dependencies. ​ +Backstage is an open platform, but you are in control of your own data. You +control who has access to any data you provide to your version of Backstage and +who that data is shared with. ​ + +### Can Backstage be used to build something other than a developer portal? + +​ Yes. The core frontend framework could be used for building any large-scale +web application where (1) multiple teams are building separate parts of the app, +and (2) you want the overall experience to be consistent. ​ That being said, in +[Phase 2](https://github.com/spotify/backstage#project-roadmap) of the project +we will add features that are needed for developer portals and systems for +managing software ecosystems. Our ambition will be to keep Backstage modular. ​ + +### How can I get involved? + +​ Jump right in! Come help us fix some of the +[early bugs and first issues](https://github.com/spotify/backstage/labels/good%20first%20issue) +or reach [a new milestone](https://github.com/spotify/backstage/milestones). Or +write an open source plugin for Backstage, like this +[Lighthouse plugin](https://github.com/spotify/backstage/tree/master/plugins/lighthouse). +​ See all the ways you can +[contribute here](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md). +We'd love to have you as part of the community. ​ + +### Can I join the Backstage team? + +​ If you're interested in being part of the Backstage team, reach out to +[fossopportunities@spotify.com](mailto:fossopportunities@spotify.com) diff --git a/docs/README.md b/docs/README.md index 28d1047942..89160ce9e5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,3 +7,4 @@ Check out or see the table of content below. - [References](reference/README.md) - [Publishing](publishing.md) - [Designing for Backstage](design.md) +- [How to add an auth provider](auth/add-auth-provider.md) diff --git a/docs/architecture-decisions/adr002-default-catalog-file-format.md b/docs/architecture-decisions/adr002-default-catalog-file-format.md index 23db32cc9c..6cf649aedf 100644 --- a/docs/architecture-decisions/adr002-default-catalog-file-format.md +++ b/docs/architecture-decisions/adr002-default-catalog-file-format.md @@ -6,22 +6,22 @@ ## Background -Backstage comes with a software catalog functionality, that you can use to -track all your software components and more. It can be powered by data from -various sources, and one of them that is included with the package, is a -custom database backed catalog. It has the ability to keep itself updated -automatically based on the contents of little descriptor files in your -version control system of choice. Developers create these files and maintain -them side by side with their code, and the catalog system reacts accordingly. +Backstage comes with a software catalog functionality, that you can use to track +all your software components and more. It can be powered by data from various +sources, and one of them that is included with the package, is a custom database +backed catalog. It has the ability to keep itself updated automatically based on +the contents of little descriptor files in your version control system of +choice. Developers create these files and maintain them side by side with their +code, and the catalog system reacts accordingly. This ADR describes the default format of these descriptor files. ### Inspiration -Internally at Spotify, a home grown software catalog system is used heavily -and forms a core part of Backstage and other important pieces of the -infrastructure. The user experience, learnings and certain pieces of metadata -from that catalog are being carried over to the open source effort. +Internally at Spotify, a home grown software catalog system is used heavily and +forms a core part of Backstage and other important pieces of the infrastructure. +The user experience, learnings and certain pieces of metadata from that catalog +are being carried over to the open source effort. The file format described herein, also draws heavy inspiration from the [kubernetes object format](https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/). @@ -35,9 +35,9 @@ inside Backstage, or by push events from a CI/CD pipelines, or by webhook triggers from the version control system, etc. Each file describes one or more entities in accordance with the -[Backstage System Model](https://github.com/spotify/backstage/issues/390). -All of these entities have a common stucture and nomenclature, and they are -stored in the software catalog from which they then can be queried. +[Backstage System Model](https://github.com/spotify/backstage/issues/390). All +of these entities have a common stucture and nomenclature, and they are stored +in the software catalog from which they then can be queried. Entities have distinct names, and they may reference each other by those names. @@ -70,9 +70,9 @@ spec: ``` The root fields `apiVersion`, `kind`, `metadata`, and `spec` are part of the -_envelope_, defining the overall structure of all kinds of entity. Likewise, -the `name`, `namespace`, `labels`, and `annotations` metadata fields are of -special significance and have reserved purposes and distinct shapes. +_envelope_, defining the overall structure of all kinds of entity. Likewise, the +`name`, `namespace`, `labels`, and `annotations` metadata fields are of special +significance and have reserved purposes and distinct shapes. See below for details about these fields. @@ -88,30 +88,31 @@ first versions of the catalog will focus on the `Component` kind. The `apiVersion`is the version of specification format for that particular entity that this file is written against. The version is used for being able to -evolve the format, and the tuple of `apiVersion` and `kind` should be enough -for a parser to know how to interpret the contents of the rest of the document. +evolve the format, and the tuple of `apiVersion` and `kind` should be enough for +a parser to know how to interpret the contents of the rest of the document. Backstage specific entities have an `apiVersion` that is prefixed with -`backstage.io/`, to distinguish them from other types of object that share -the same type of structure. This may be relevant when co-hosting these +`backstage.io/`, to distinguish them from other types of object that share the +same type of structure. This may be relevant when co-hosting these specifications with e.g. kubernetes object manifests. -Early versions of the catalog will be using beta versions, e.g. `backstage.io/v1beta1`, -to signal that the format may still change. After that, we will be using -`backstage.io/v1` and up. +Early versions of the catalog will be using beta versions, e.g. +`backstage.io/v1beta1`, to signal that the format may still change. After that, +we will be using `backstage.io/v1` and up. ### `metadata` -A structure that contains metadata about the entity, i.e. things that aren't directly -part of the entity specification itself. See below for more details about this structure. +A structure that contains metadata about the entity, i.e. things that aren't +directly part of the entity specification itself. See below for more details +about this structure. ### `spec` The actual specification data that describes the entity. -The precise structure of the `spec` depends on the `apiVersion` and `kind` combination, -and some kinds may not even have a `spec` at all. See further down in this document for -the specification structure of specific kinds. +The precise structure of the `spec` depends on the `apiVersion` and `kind` +combination, and some kinds may not even have a `spec` at all. See further down +in this document for the specification structure of specific kinds. ## Metadata @@ -119,27 +120,31 @@ The `metadata` root field has the following nested structure. ### `name` -The name of the entity. This name is both meant for human eyes to recognize the entity, -and for machines and other components to reference the entity (e.g. in URLs or from -other entity specification files). +The name of the entity. This name is both meant for human eyes to recognize the +entity, and for machines and other components to reference the entity (e.g. in +URLs or from other entity specification files). -Names must be unique per kind, within a given namespace (if specified), at any point in -time. Names may be reused at a later time, after an entity is deleted from the registry. +Names must be unique per kind, within a given namespace (if specified), at any +point in time. Names may be reused at a later time, after an entity is deleted +from the registry. -Names are required to follow a certain format. Entities that do not follow those rules -will not be accepted for registration in the catalog. The ruleset is configurable to fit -your organization's needs, but the default behavior is as follows. +Names are required to follow a certain format. Entities that do not follow those +rules will not be accepted for registration in the catalog. The ruleset is +configurable to fit your organization's needs, but the default behavior is as +follows. - Strings of length at least 1, and at most 63 -- Must consist of sequences of `[a-z0-9A-Z]` possibly separated by one of `[-_.]` +- Must consist of sequences of `[a-z0-9A-Z]` possibly separated by one of + `[-_.]` Example: `visits-tracking-service`, `CircleciBuildsDs_avro_gcs` -In addition to this, names are passed through a normalization function and then compared -to the same normalized form of other entity names and made sure to not collide. This rule -of uniqueness exists to avoid situations where e.g. both `my-component` and `MyComponent` -are registered side by side, which leads to confusion and risk. The normalization function -is also configurable, but the default behavior is as follows. +In addition to this, names are passed through a normalization function and then +compared to the same normalized form of other entity names and made sure to not +collide. This rule of uniqueness exists to avoid situations where e.g. both +`my-component` and `MyComponent` are registered side by side, which leads to +confusion and risk. The normalization function is also configurable, but the +default behavior is as follows. - Strip out all characters outside of the set `[a-zA-Z0-9]` - Convert to lowercase @@ -148,55 +153,59 @@ Example: `CircleciBuildsDs_avro_gcs` -> `circlecibuildsdsavrogcs` ### `namespace` -The `name` of a namespace that the entity belongs to. This field is optional, and currently -has no special semantics apart from bounding the name uniqueness constraint if specified. -It is reserved for future use and may get broader semantic implication. +The `name` of a namespace that the entity belongs to. This field is optional, +and currently has no special semantics apart from bounding the name uniqueness +constraint if specified. It is reserved for future use and may get broader +semantic implication. Namespaces may also be part of the catalog, and are `v1` / `Namespace` entities, i.e. not Backstage specific but the same as in Kubernetes. ### `description` -A human readable description of the entity, to be shown in Backstage. Should be kept short -and informative, suitable to give an overview of the entity's purpose at a glance. More -detailed explanations and documentation should be placed elsewhere. +A human readable description of the entity, to be shown in Backstage. Should be +kept short and informative, suitable to give an overview of the entity's purpose +at a glance. More detailed explanations and documentation should be placed +elsewhere. ### `labels` -Labels are optional key/value pairs of that are attached to the entity, and their use is -identical to [kubernetes object labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). +Labels are optional key/value pairs of that are attached to the entity, and +their use is identical to +[kubernetes object labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/). -Their main purpose is for references to other entities, and for information that is -in one way or another classifying for the current entity. They are often used as values -in queries or filters. +Their main purpose is for references to other entities, and for information that +is in one way or another classifying for the current entity. They are often used +as values in queries or filters. Both the key and the value are strings, subject to the following restrictions. -Keys have an optional prefix followed by a slash, and then the name part which is required. -The prefix must be a valid lowercase domain name, at most 253 characters in total. The name -part must be sequences of `[a-zA-Z0-9]` separated by any of `[-_.]`, at most 63 characters -in total. +Keys have an optional prefix followed by a slash, and then the name part which +is required. The prefix must be a valid lowercase domain name, at most 253 +characters in total. The name part must be sequences of `[a-zA-Z0-9]` separated +by any of `[-_.]`, at most 63 characters in total. -The `backstage.io/` prefix is reserved for use by Backstage core components. Some keys such as -`system` also have predefined semantics. +The `backstage.io/` prefix is reserved for use by Backstage core components. +Some keys such as `system` also have predefined semantics. Values are strings that follow the same restrictions as `name` above. ### `annotations` An object with arbitrary non-identifying metadata attached to the entity, -identical in use to [kubernetes object annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/). +identical in use to +[kubernetes object annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/). -Their purpose is mainly, but not limited, to reference into external systems. This could -for example be a reference to the git ref the entity was ingested from, to monitoring -and logging systems, to pagerduty schedules, etc. +Their purpose is mainly, but not limited, to reference into external systems. +This could for example be a reference to the git ref the entity was ingested +from, to monitoring and logging systems, to pagerduty schedules, etc. Both the key and the value are strings, subject to the following restrictions. -Keys have an optional prefix followed by a slash, and then the name part which is required. -The prefix must be a valid lowercase domain name, at most 253 characters in total. The name -part must be sequences of `[a-zA-Z0-9]` separated by any of `[-_.]`, at most 63 characters -in total. +Keys have an optional prefix followed by a slash, and then the name part which +is required. The prefix must be a valid lowercase domain name, at most 253 +characters in total. The name part must be sequences of `[a-zA-Z0-9]` separated +by any of `[-_.]`, at most 63 characters in total. The `backstage.io/` prefix is reserved for use by Backstage core components. diff --git a/docs/architecture-decisions/adr003-avoid-default-exports.md b/docs/architecture-decisions/adr003-avoid-default-exports.md index 20a402e9c2..7becebaa4b 100644 --- a/docs/architecture-decisions/adr003-avoid-default-exports.md +++ b/docs/architecture-decisions/adr003-avoid-default-exports.md @@ -6,30 +6,50 @@ ## Context -When CommonJS was the primary authoring format, the best practice was to export only one thing from a module using the `module.exports = ...` format. This aligned with the [UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) of "Do one thing well". The module would be consumed (`const localName = require('the-module');`) without having to know the internal structure. +When CommonJS was the primary authoring format, the best practice was to export +only one thing from a module using the `module.exports = ...` format. This +aligned with the +[UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) of "Do one +thing well". The module would be consumed +(`const localName = require('the-module');`) without having to know the internal +structure. -Now, ESModules are the primary authoring format. They have numerous benefits, such as compile-time verification of exports, and standards-defined semantics. They have a similar mechanism known as "default exports", which allows for a consumer to `import localName from 'the-module';`. This is implicitly the same as `import { default as localName } from 'the-module';`. +Now, ESModules are the primary authoring format. They have numerous benefits, +such as compile-time verification of exports, and standards-defined semantics. +They have a similar mechanism known as "default exports", which allows for a +consumer to `import localName from 'the-module';`. This is implicitly the same +as `import { default as localName } from 'the-module';`. -However, there are numerous reasons to avoid default exports, as documented by others before: +However, there are numerous reasons to avoid default exports, as documented by +others before: - https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/ A summary: -- They add indirection by encouraging a developer to create local names for modules, increasing cognitive load and slowing down code comprehension: `import TheListThing from 'not-a-list-thing';`. -- They thwart tools, such as IDEs, that can automatically rename and refactor code. -- They promote typos and mistakes, as the imported member is completely up to the consuming developer to define. -- They are ugly in CommonJS interop, as the default property must be manually specified by the consumer. This is often hidden by Babel's module interop. -- They break re-exports due to name conflicts, forcing the developer to manually name each. +- They add indirection by encouraging a developer to create local names for + modules, increasing cognitive load and slowing down code comprehension: + `import TheListThing from 'not-a-list-thing';`. +- They thwart tools, such as IDEs, that can automatically rename and refactor + code. +- They promote typos and mistakes, as the imported member is completely up to + the consuming developer to define. +- They are ugly in CommonJS interop, as the default property must be manually + specified by the consumer. This is often hidden by Babel's module interop. +- They break re-exports due to name conflicts, forcing the developer to manually + name each. -Using named exports helps prevent needing to rename symbols, which has myriad benefts. A few are: +Using named exports helps prevent needing to rename symbols, which has myriad +benefts. A few are: - IDE tools like "Find All References" and "Go To Definition" function - Manual codebase searching ("grep", etc) is easier with a unique symbol ## Decision -We will stop using default exports except when absolutely necessary (such as [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules). A workaround exists for those that would prefer to never use `default`: +We will stop using default exports except when absolutely necessary (such as +[`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules). +A workaround exists for those that would prefer to never use `default`: ```ts const Component = React.lazy(() => @@ -39,6 +59,9 @@ const Component = React.lazy(() => ## Consequences -We will actively work to remove them from our codebases, being as explicit as possible. Have a connected component? `export const ConnectedComponent = connect(Component)`. +We will actively work to remove them from our codebases, being as explicit as +possible. Have a connected component? +`export const ConnectedComponent = connect(Component)`. -We will add tools, such as lint rules, to help migrate away from default exports. +We will add tools, such as lint rules, to help migrate away from default +exports. diff --git a/docs/architecture-decisions/adr004-module-export-structure.md b/docs/architecture-decisions/adr004-module-export-structure.md index 56c675f069..54132773c9 100644 --- a/docs/architecture-decisions/adr004-module-export-structure.md +++ b/docs/architecture-decisions/adr004-module-export-structure.md @@ -6,7 +6,8 @@ ## Context -With a growing number of exports of packages like `@backstage/core`, it is becoming more and more difficult to answer questions such as +With a growing number of exports of packages like `@backstage/core`, it is +becoming more and more difficult to answer questions such as > Is the export in this module also exported by the package? @@ -14,12 +15,19 @@ or > What is exported from this directory? -We currently do not use any pattern for how to structure exports. There is a mix of package-level re-exports deep into the directory tree, shallow re-exports for each directory, exports using `*` and explicit lists of each symbol, etc. -The mix and lack of predictability makes it difficult to reason about the boundaries of a module, and for example knowing whether is is safe to export a symbol in a given file. +We currently do not use any pattern for how to structure exports. There is a mix +of package-level re-exports deep into the directory tree, shallow re-exports for +each directory, exports using `*` and explicit lists of each symbol, etc. The +mix and lack of predictability makes it difficult to reason about the boundaries +of a module, and for example knowing whether is is safe to export a symbol in a +given file. ## Decision -We will make each exported symbol traceable through index files all the way down to the root of the package, `src/index.ts`. Each index file will only re-export from its own immediate directory children, and only index files will have re-exports. This gives a file tree similar to this: +We will make each exported symbol traceable through index files all the way down +to the root of the package, `src/index.ts`. Each index file will only re-export +from its own immediate directory children, and only index files will have +re-exports. This gives a file tree similar to this: ```text index.ts @@ -33,16 +41,25 @@ lib/index.ts /helper.ts ``` -To check whether for example `SubComponentY` is exported from the package, it should be possible to traverse the index files towards the root, starting at the adjacent one. If there is any index file that doesn't export the previous one, the symbol is not publicly exported. For example, if `components/ComponentX/index.ts` exports `SubComponentY`, but `components/index.ts` does not re-export `./ComponentX`, one should be certain that `SubComponentY` is not exported outside the package. This rule would be broken if for example the root `index.ts` re-exports `./components/ComponentX` +To check whether for example `SubComponentY` is exported from the package, it +should be possible to traverse the index files towards the root, starting at the +adjacent one. If there is any index file that doesn't export the previous one, +the symbol is not publicly exported. For example, if +`components/ComponentX/index.ts` exports `SubComponentY`, but +`components/index.ts` does not re-export `./ComponentX`, one should be certain +that `SubComponentY` is not exported outside the package. This rule would be +broken if for example the root `index.ts` re-exports `./components/ComponentX` -In addition, index files that are re-exporting other index files should always use wildcard form, that is: +In addition, index files that are re-exporting other index files should always +use wildcard form, that is: ```ts // in components/index.ts export * from './ComponentX'; ``` -Index files that are re-exporting symbols from non-index files should always enumerate all exports, that is: +Index files that are re-exporting symbols from non-index files should always +enumerate all exports, that is: ```ts // in components/ComponentX/index.ts @@ -50,14 +67,16 @@ export { ComponentX } from './ComponentX'; export type { ComponentXProps } from './ComponentX'; ``` -Internal cross-directory imports are allowed from non-index modules to index modules, for example: +Internal cross-directory imports are allowed from non-index modules to index +modules, for example: ```ts // in components/ComponentX/ComponentX.tsx import { UtilityX } from '../../lib/UtilityX'; ``` -Imports that bypass an index file are discouraged, but may sometimes be necessary, for example: +Imports that bypass an index file are discouraged, but may sometimes be +necessary, for example: ```ts // in components/ComponentX/ComponentX.tsx @@ -66,6 +85,9 @@ import { helperFunc } from '../../lib/UtilityX/helper'; ## Consequences -We will actively work to rework the export structure in our codebase, prioritizing the library packages such as `@backstage/core` and `@backstage/backend-common`. +We will actively work to rework the export structure in our codebase, +prioritizing the library packages such as `@backstage/core` and +`@backstage/backend-common`. -If possible, we will add tools, such as lint rules, to help enforce the export structure. +If possible, we will add tools, such as lint rules, to help enforce the export +structure. diff --git a/docs/architecture-decisions/adr005-catalog-core-entities.md b/docs/architecture-decisions/adr005-catalog-core-entities.md index ca2a66f6b9..578eb72b91 100644 --- a/docs/architecture-decisions/adr005-catalog-core-entities.md +++ b/docs/architecture-decisions/adr005-catalog-core-entities.md @@ -6,7 +6,8 @@ ## Context -We want to standardize on a few core entities that we are tracking in the Backstage catalog. This allows us to build specific plugins around them. +We want to standardize on a few core entities that we are tracking in the +Backstage catalog. This allows us to build specific plugins around them. ## Decision @@ -14,17 +15,26 @@ Backstage should eventually support the following core entities: - **Components** are individual pieces of software - **APIs** are the boundaries between different components -- **Resources** are physical or virtual infrastructure needed to operate a component +- **Resources** are physical or virtual infrastructure needed to operate a + component ![Catalog Core Entities](catalog-core-entities.png) -For now, we'll start by only implementing support for the Component entity in the Backstage catalog. This can later be extended to APIs, Resources and other potentially useful entities. +For now, we'll start by only implementing support for the Component entity in +the Backstage catalog. This can later be extended to APIs, Resources and other +potentially useful entities. ### Component -A component is a piece of software, for example a mobile application feature, web site, backend service or data pipeline (list not exhaustive). A component can be tracked in source control, or use some existing open source or commercial software. It can implement APIs for other components to consume. In turn it might depend on APIs implemented by other components, or resources that are attached to it at runtime. +A component is a piece of software, for example a mobile application feature, +web site, backend service or data pipeline (list not exhaustive). A component +can be tracked in source control, or use some existing open source or commercial +software. It can implement APIs for other components to consume. In turn it +might depend on APIs implemented by other components, or resources that are +attached to it at runtime. -Component entities are typically defined in YAML descriptor files next to the code of the component, and could look like this (actual schema will evolve): +Component entities are typically defined in YAML descriptor files next to the +code of the component, and could look like this (actual schema will evolve): ```yaml apiVersion: backstage.io/v1beta1 @@ -37,11 +47,20 @@ spec: ### API -APIs form an abstraction that allows large software ecosystems to scale. Thus, APIs are a first class citizen in the Backstage model and the primary way to discover existing functionality in the ecosystem. +APIs form an abstraction that allows large software ecosystems to scale. Thus, +APIs are a first class citizen in the Backstage model and the primary way to +discover existing functionality in the ecosystem. -APIs are implemented by components and make their boundaries explicit. They might be defined using an RPC IDL (e.g. in Protobuf, GraphQL or similar), a data schema (e.g. in Avro, TFRecord or similar), or as code interfaces (e.g. framework APIs in Swift, Kotlin, Java, C++, Typescript etc). In any case, APIs exposed by components need to be in a known machine-readable format so we can build further tooling and analysis on top. +APIs are implemented by components and make their boundaries explicit. They +might be defined using an RPC IDL (e.g. in Protobuf, GraphQL or similar), a data +schema (e.g. in Avro, TFRecord or similar), or as code interfaces (e.g. +framework APIs in Swift, Kotlin, Java, C++, Typescript etc). In any case, APIs +exposed by components need to be in a known machine-readable format so we can +build further tooling and analysis on top. -APIs are typically indexed from existing definitions in source control and thus wouldn't need their own descriptor files, but would be stored in the catalog somewhat like this (actual schema will evolve): +APIs are typically indexed from existing definitions in source control and thus +wouldn't need their own descriptor files, but would be stored in the catalog +somewhat like this (actual schema will evolve): ```yaml apiVersion: backstage.io/v1beta1 @@ -54,9 +73,11 @@ spec: service HelloService { rpc SayHello (HelloRequest) returns (HelloResponse); } + message HelloRequest { string greeting = 1; } + message HelloResponse { string reply = 1; } @@ -64,9 +85,16 @@ spec: ### Resource -Resources are the infrastructure your software needs to operate at runtime like Bigtable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together with components and APIs will allow us to visualize and create tooling around them in Backstage. +Resources are the infrastructure your software needs to operate at runtime like +Bigtable databases, Pub/Sub topics, S3 buckets or CDNs. Modelling them together +with components and APIs will allow us to visualize and create tooling around +them in Backstage. -Resources are typically indexed from declarative definitions (e.g. Terraform, GCP Config Connector, AWS Cloud Formation) and/or inventories from cloud providers (e.g. GCP Asset Inventory) and thus wouldn't need their own descriptor files, but would be stored in the catalog somewhat like this (actual schema will evolve): +Resources are typically indexed from declarative definitions (e.g. Terraform, +GCP Config Connector, AWS Cloud Formation) and/or inventories from cloud +providers (e.g. GCP Asset Inventory) and thus wouldn't need their own descriptor +files, but would be stored in the catalog somewhat like this (actual schema will +evolve): ```yaml apiVersion: backstage.io/v1beta1 @@ -80,4 +108,5 @@ spec: ## Consequences -We will continue fleshing out support for the Component entity in the Backstage catalog. +We will continue fleshing out support for the Component entity in the Backstage +catalog. diff --git a/docs/architecture-terminology.md b/docs/architecture-terminology.md index 1570607896..836b2cfa99 100644 --- a/docs/architecture-terminology.md +++ b/docs/architecture-terminology.md @@ -1,9 +1,18 @@ # Architecture and Terminology -Backstage is constructed out of three parts. We separate Backstage in this way because we see three groups of contributors that work with Backstage in three different ways. +Backstage is constructed out of three parts. We separate Backstage in this way +because we see three groups of contributors that work with Backstage in three +different ways. - Core - Base functionality built by core devs in the open source project. -- App - The app is an instance of a Backstage app that is deployed and tweaked. The app ties together core functionality with additional plugins. The app is built and maintained by app developers, usually a productivity team within a company. -- Plugins - Additional functionality to make your Backstage app useful for your company. Plugins can be specific to a company or open sourced and reusable. At Spotify we have over 100 plugins built by over 50 different teams. It has been very powerful to get contributions from various infrastructure teams added into a single unified developer experience. +- App - The app is an instance of a Backstage app that is deployed and tweaked. + The app ties together core functionality with additional plugins. The app is + built and maintained by app developers, usually a productivity team within a + company. +- Plugins - Additional functionality to make your Backstage app useful for your + company. Plugins can be specific to a company or open sourced and reusable. At + Spotify we have over 100 plugins built by over 50 different teams. It has been + very powerful to get contributions from various infrastructure teams added + into a single unified developer experience. [Back to Docs](README.md) diff --git a/docs/auth/add-auth-provider.md b/docs/auth/add-auth-provider.md new file mode 100644 index 0000000000..33f1d335a0 --- /dev/null +++ b/docs/auth/add-auth-provider.md @@ -0,0 +1,229 @@ +# Adding authentication providers + +## Passport + +We chose [Passport](http://www.passportjs.org/) as our authentication platform +due to its comprehensive set of supported authentication +[strategies](http://www.passportjs.org/packages/). + +## How to add a new strategy provider + +### Quick guide + +[1.](#installing-the-dependencies) Install the passport-js based provider +package. + +[2.](#create-implementation) Create a new folder structure for the provider. + +[3.](#adding-an-oauth-based-provider) Implement the provider, extending the +suitable framework if needed. + +[4.](#hook-it-up-to-the-backend) Add the provider to the backend. + +### Installing the dependencies: + +```bash +cd plugins/auth-backend +yarn add passport-provider-a +yarn add @types/passport-provider-a +``` + +### Create implementation + +Make a new folder with the name of the provider following the below file +structure: + +```bash +plugins/auth-backend/src/providers/providerA +├── index.ts +└── provider.ts +``` + +**`plugins/auth-backend/src/providers/providerA/provider.ts`** defines the +provider class which implements a handler for the chosen framework. + +#### Adding an OAuth based provider + +If we're adding an `OAuth` based provider we would implement the +[OAuthProviderHandlers](#OAuthProviderHandlers) interface. + +The provider class takes the provider's configuration as a class parameter. It +also imports the `Strategy` from the passport package. + +```ts +import { Strategy as ProviderAStrategy } from 'passport-provider-a'; + +export class ProviderAAuthProvider implements OAuthProviderHandlers { + private readonly providerConfig: AuthProviderConfig; + private readonly _strategy: ProviderAStrategy; + + constructor(providerConfig: AuthProviderConfig) { + this.providerConfig = providerConfig; + this._strategy = new ProviderAStrategy( + { ...providerConfig.options }, + verifyFunction, // See the "Verify Callback" section + ); + } + + async start() {} + async handler() {} +} +``` + +#### Adding an non-OAuth based provider + +_**Note**: We have prioritized OAuth-based providers and non-OAuth providers +should be considered experimental._ + +An non-`OAuth` based provider could implement +[AuthProviderRouteHandlers](#AuthProviderRouteHandlers) instead. + +```ts +export class ProviderAAuthProvider implements AuthProviderRouteHandlers { + private readonly providerConfig: AuthProviderConfig; + private readonly _strategy: ProviderAStrategy; + + constructor(providerConfig: AuthProviderConfig) { + this.providerConfig = providerConfig; + this._strategy = new ProviderAStrategy( + { ...providerConfig.options }, + verifyFunction, // See the "Verify Callback" section + ); + } + + async start() {} + async frameHandler() {} + async logout() {} + async refresh() {} // If supported +} +``` + +#### Create method + +Each provider exports a create method that creates the provider instance, +optionally extending a supported authorization framework. This method exists to +allow for flexibility if additional frameworks are supported in the future. + +Implementing OAuth by returning an instance of `OAuthProvider` based of the +provider's class: + +```ts +export function createProviderAProvider(config: AuthProviderConfig) { + const provider = new ProviderAAuthProvider(config); + const oauthProvider = new OAuthProvider(provider, config.provider, true); + return oauthProvider; +} +``` + +Not extending with OAuth, the main difference here is that the create method is +returning a instance of the class without adding the OAuth authorization +framework to it. + +```ts +export function createProviderAProvider(config: AuthProviderConfig) { + return new ProviderAAuthProvider(config); +} +``` + +#### Verify Callback + +> Strategies require what is known as a verify callback. The purpose of a verify +> callback is to find the user that possesses a set of credentials. When +> Passport authenticates a request, it parses the credentials contained in the +> request. It then invokes the verify callback with those credentials as +> arguments [...]. If the credentials are valid, the verify callback invokes +> done to supply Passport with the user that authenticated. +> +> If the credentials are not valid (for example, if the password is incorrect), +> done should be invoked with false instead of a user to indicate an +> authentication failure. +> +> http://www.passportjs.org/docs/configure/ + +**`plugins/auth-backend/src/providers/providerA/index.ts`** is simply +re-exporting the create method to be used for hooking the provider up to the +backend. + +```ts +export { createProviderAProvider } from './provider'; +``` + +### Hook it up to the backend + +**`plugins/auth-backend/src/providers/config.ts`** The provider needs to be +configured properly so you need to add it to the list of configured providers, +all of which implement [AuthProviderConfig](#AuthProviderConfig): + +```ts +export const providers = [ + { + provider: 'providerA', # used as an identifier + options: { ... }, # consult the provider documentation for which options you should provide + disableRefresh: true # if the provider lacks refresh tokens + }, +``` + +**`plugins/auth-backend/src/providers/factories.ts`** When the `auth-backend` +starts it sets up routing for all the available providers by calling +`createAuthProviderRouter` on each provider. You need to import the create +method from the provider and add it to the factory: + +```ts +import { createProviderAProvider } from './providerA'; +const factories: { [providerId: string]: AuthProviderFactory } = { + providerA: createProviderAProvider, +}; +``` + +By doing this `auth-backend` automatically adds these endpoints: + +```ts +router.get('/auth/providerA/start'); +router.get('/auth/providerA/handler/frame'); +router.post('/auth/providerA/handler/frame'); +router.post('/auth/providerA/logout'); +router.get('/auth/providerA/refresh'); // if supported +``` + +As you can see each endpoint is prefixed with both `/auth` and its provider +name. + +### Test the new provider + +You can `curl -i localhost:7000/auth/providerA/start` and which should provide a +`302` redirect with a `Location` header. Paste the url from that header into a +web browser and you should be able to trigger the authorization flow. + +--- + +##### OAuthProviderHandlers + +```ts +export interface OAuthProviderHandlers { + start(req: express.Request, options: any): Promise; + handler(req: express.Request): Promise; + refresh?(refreshToken: string, scope: string): Promise; + logout?(): Promise; +} +``` + +##### AuthProviderRouteHandlers + +```ts +export interface AuthProviderRouteHandlers { + start(req: express.Request, res: express.Response): Promise; + frameHandler(req: express.Request, res: express.Response): Promise; + refresh?(req: express.Request, res: express.Response): Promise; + logout(req: express.Request, res: express.Response): Promise; +} +``` + +##### AuthProviderConfig + +```ts +export type AuthProviderConfig = { + provider: string; + options: any; + disableRefresh?: boolean; +}; +``` diff --git a/docs/auth/glossary.md b/docs/auth/glossary.md new file mode 100644 index 0000000000..6408587273 --- /dev/null +++ b/docs/auth/glossary.md @@ -0,0 +1,26 @@ +# Glossary + +- **Popup** - A separate browser window opened on top of the previous one. +- **OAuth** - More specifically OAuth 2.0, a standard protocol for + authorization. See [oauth.net/2/](https://oauth.net/2/). +- **OpenID Connect** - A layer on top of OAuth which standardises + authentication. See + [en.wikipedia.org/wiki/OpenID_Connect](https://en.wikipedia.org/wiki/OpenID_Connect). +- **JWT** - JSON Web Token, a popular JSON based token format that is commonly + encrypted and/or signed, see + [en.wikipedia.org/wiki/JSON_Web_Token](https://en.wikipedia.org/wiki/JSON_Web_Token) +- **Scope** - A string that describes a certain type of access that can be + granted to a user using OAuth. +- **Access token** - A token that gives access to perform actions on behalf of a + user. It will commonly have a short expiry time, and be limited to a set of + scopes. Part of the OAuth protocol. +- **ID token** - A JWT used to prove a user's identity, containing for example + the user's email. Part of OpenID Connect. +- **Offline access** - OAuth flow that results in both a refresh and access + token, where the refresh token has a long expiration or never expires, and can + be used to request more access tokens in the future. This lets the user go + "offline" with respect to the token issuer, but still be able to request more + tokens at a later time without further direct interaction for the user. +- **Code grant** - OAuth flow where the client receives an authorization code + that is passed to the backend to be exchanged for an access token and possibly + refresh token. diff --git a/docs/auth/oauth-popup-flow.svg b/docs/auth/oauth-popup-flow.svg new file mode 100644 index 0000000000..4d9e79787a --- /dev/null +++ b/docs/auth/oauth-popup-flow.svg @@ -0,0 +1,56 @@ +OAuth Consent and Refresh FlowBrowserBrowserPopup WindowPopup Windowauth-backend pluginauth-backend pluginConsent ScreenConsent ScreenOAuth ProviderOAuth ProviderComponents on page ask for anaccess token with greaterscope than the existing session.Open popupGET /auth/<provider>/start?scope=some%20scopesRedirect to consent screen withrandom nonce in OAuth state andshort-lived cookie with the same nonce.GET /consent_url?redirect_uri=<redirect_uri>?nonce=<n>where redirect_uri=<app-origin>/auth/<provider>/handler/frameUser consents toaccess the new scope.Redirect to given redirect URL, with authorization codeGET /auth/<provider>/handler/frame?code=<c>&nonce=<n>Request includes the previously set none cookieVerify that the nonce in the cookiematches the nonce in the OAuth stateAuthorization CodeClient IDClient SecretVerify and generate tokensAccess Token(ID Token)(Refresh Token)ScopeExpire TimeSmall HTML page with inlined response payloadStore Refresh Token in HTTP-only cookiepostMessage() with tokens and info or errorClose selfA later point when a refreshis needed. Either because ofa reload or an expiring session.GET /auth/<provider>/tokenRefresh Token cookie includedRefresh TokenClient IDClient SecretAccess Token(ID Token)ScopeExpire TimeTokens and info \ No newline at end of file diff --git a/docs/auth/oauth.md b/docs/auth/oauth.md new file mode 100644 index 0000000000..519cfe1d85 --- /dev/null +++ b/docs/auth/oauth.md @@ -0,0 +1,151 @@ +# OAuth and OpenID Connect + +This section describes how Backstage allows plugins to request OAuth Access +Tokens and OpenID Connect ID Tokens on behalf of the user, to be used for auth +to various third party APIs. + +## Summary + +There are occasions when the user wants to perform actions towards third party +services that require authorization via OAuth. Backstage provides standardized +[Utility APIs](../getting-started/utility-apis.md) such as the +[GoogleAuthApi](../../packages/core-api/src/apis/definitions/auth.ts) for that +use-case. Backstage also includes a set of implementations of these APIs that +integrate with the [auth-backend](../../plugins/auth-backend) plugin to provide +a popup-based OAuth flow. + +## Background + +Access control in OAuth is implemented in terms of scope, which is a list of +permissions given to the app. An OAuth service can issue Access Tokens that are +tied to a certain set of scopes, such as viewing profile information, reading +and/or writing user data in the service. The scope format and handling is +specific to each OAuth provider, and the set of available scopes are typically +found in the documentation describing the auth solution of the provider, for +example +[developers.google.com/identity/protocols/oauth2/scopes](https://developers.google.com/identity/protocols/oauth2/scopes). + +As a part of logging in with an OAuth provider, the user needs to consent to +both the login itself and the set of scopes that the app is requesting to use. +This is done by loading a page provided by the OAuth provider, where a user can +choose an account to log in with, and accept or reject the request. If the user +accepts the login request, a token is issued, and any holder of the token can +use it to make authenticated requests towards the third party service. + +## OAuth in @backstage/core-api and auth-backend + +The default OAuth implementation in Backstage is based on an OAuth server-side +offline access flow, which means that it uses the backend as a helper in order +to trade credentials. A benefit of this type of flow is that it does not require +the use of third party cookies, and is robust on a wide selection of browsers +and privacy browsing plugins, strict security settings, etc. + +The implementation also uses a popup-based flow, where auth requests are handled +in a new popup window that is opened by the app. By using a popup-based flow it +is possible to request authentication at any point in the app, without requiring +a redirect. Because of this there is no need to ask for all scopes upfront, or +interrupt the app with a redirect and forcing plugin authors to take care in +restoring state after a redirect has been make. All in all it makes it much +easier to make authenticated requests inside a plugin. + +## OAuth Flow + +The following describes the OAuth flow implemented by the +[auth-backend](../../plugins/auth-backend) and +[DefaultAuthConnector](../../packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts) +in `@backstage/core-api`. + +Component and APIs can request Access or ID Tokens from any available Auth +provider. If there already exists a cached fresh token that covers (at least) +the requested scopes, it will be returned immediately. If the OAuth provider +implements token refreshes, this check will also trigger a token refresh attempt +if no session is a available. + +If new scopes are requested, or the user is not yet logged in with that +provider, a dialog is shown informing the user that they need to log in with the +specified provider. If the user agrees to continue, a separate popup window is +opened that implements the entire consent flow. + +The popup window is pointed to the `/start` endpoint of the auth provider in the +`auth-backend` plugin, which then redirects to the OAuth consent screen of the +provider. The consent screen is controlled by the OAuth provider, and will do +things like prompting the user to log in with an account, and possibly reviewing +the set of requested scopes. If the login request is accepted, the popup window +will be redirected back to the `/handler/frame` endpoint of the auth backend. +The redirect URL will contain a short-term authorization code, which is picked +up by the backend and exchanged for long-term tokens via a call to the OAuth +provider. The Access and possibly ID Token is then handed back to the main +Backstage page via `postMessage`. If the OAuth provider implements offline +refresh, a refresh token will be stored in an HTTP-only cookie scoped to the +specific provider in the `auth-backend` plugin. + +To protect against certain attacks, the above flow also includes a simple nonce +check and a lightweight CSRF protection header. The nonce check is done to +protect against attacks where an attacker tricks a user to log in with an +account of the attacker's choosing in order to gather data. In the first part of +the flow where the popup is directed to the `/start` endpoint, a nonce is +generated and placed in both a cookie and the OAuth state. The nonces received +in the cookie and OAuth state in the redirect handler are then checked, and the +auth attempt will fail if they're not valid. The CSRF protection for the +`/refresh` and `/logout` endpoints is implemented by simply checking for the +presence of a `X-Requested-With` header. + +The target origin of the `postMessage` is also of importance to keep the flow +secure. It is configured to a single value for each auth provider and +environment. Without a single configured origin, any page could open a popup and +request an access token. + +### Sequence Diagram + +The following diagram visualizes the flow described in the previous section. + + + +![](oauth-popup-flow.svg) diff --git a/docs/auth/overview.md b/docs/auth/overview.md new file mode 100644 index 0000000000..2806b2dccf --- /dev/null +++ b/docs/auth/overview.md @@ -0,0 +1,44 @@ +# User Authentication and Authorization in Backstage + +## Summary + +The purpose of the Auth APIs in Backstage are to identify the user, and to +provide a way for plugins to request access to 3rd party services on behalf of +the user (OAuth). This documentation focuses on the implementation of that +solution and how to extend it. For documentation on how to consume the Auth APIs +in a plugin, see [TODO](#TODO). + +### Accessing Third Party Services + +The main pattern for talking to third party services in Backstage is +user-to-server requests, where short-lived OAuth Access Tokens are requested by +plugins to authenticate calls to external services. These calls can be made +either directly to the services or through a backend plugin or service. + +By relying on user-to-server calls we keep the coupling between the frontend and +backend low, and provide a much lower barrier for plugins to make use of third +party services. This is in comparison to for example a session-based system, +where access tokens are stored server-side. Such a solution would require a much +deeper coupling between the auth backend plugin, its session storage, and other +backend plugins or separate services. A goal of Backstage is to make it as easy +as possible to create new plugins, and an auth solution based on user-to-server +OAuth helps in that regard. + +The method with which frontend plugins request access to third party services is +through [Utility APIs](../getting-started/utility-apis.md) for each service +provider. For a full list of providers, see [TODO](#TODO). + +### Identity - TODO + +This documentation currently only covers the OAuth use-case, as identity +management is not settled yet and part of an +[upcoming milestone](https://github.com/spotify/backstage/milestone/12). + +## Further Reading + +More details are provided in dedicated sections of the documentation. + +- [OAuth](./oauth): Description of the generic OAuth flow implemented by the + [auth-backend](../../plugins/auth-backend). +- [Glossary](./glossary): Glossary of some common terms related to the auth + flows. diff --git a/docs/create-an-app.md b/docs/create-an-app.md index 8c8050c2ff..b69ff1508c 100644 --- a/docs/create-an-app.md +++ b/docs/create-an-app.md @@ -1,12 +1,16 @@ # Backstage App -To get set up quickly with your own Backstage project you can create a Backstage App. +To get set up quickly with your own Backstage project you can create a Backstage +App. -A Backstage App is a monorepo setup with `lerna` that includes everything you need to run Backstage in your own environment. +A Backstage App is a monorepo setup with `lerna` that includes everything you +need to run Backstage in your own environment. ## Create an app -To create a Backstage app, you will need to have [NodeJS](https://nodejs.org/en/download/) Active LTS Release installed (currently v12). +To create a Backstage app, you will need to have +[NodeJS](https://nodejs.org/en/download/) Active LTS Release installed +(currently v12). With `npx`: @@ -14,13 +18,15 @@ With `npx`: npx @backstage/cli create-app ``` -This will create a new Backstage App inside the current folder. The name of the app-folder is the name that was provided when prompted. +This will create a new Backstage App inside the current folder. The name of the +app-folder is the name that was provided when prompted.

create app

-Inside that directory, it will generate all the files and folder structure needed for you to run your app. +Inside that directory, it will generate all the files and folder structure +needed for you to run your app. ### Folder structure @@ -69,4 +75,5 @@ cd my-backstage-app yarn start ``` -_When `yarn start` is ready it should open up a browser window displaying your app, if not you can navigate to `http://localhost:3000`._ +_When `yarn start` is ready it should open up a browser window displaying your +app, if not you can navigate to `http://localhost:3000`._ diff --git a/docs/design.md b/docs/design.md index bcbf0bbdc5..e3816bd2e5 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,70 +1,134 @@ ![header](designheader.png) -Much like Backstage Open Source, this is a *living* document! We'll keep this updated as we evolve our practices! +Much like Backstage Open Source, this is a _living_ document! We'll keep this +updated as we evolve our practices! ## 📚 Our Philosophy ### Iterative -Backstage Open Source is a newly launched endeavor, and we’re excited to scale up our design practices! With that said, we’ll be working closely with you, the community, and iterating and experimenting as we go to see what works best. As a continual work in progress, we aspire to release early and often. Not only that, we are committed to working with developers to create a seamless and easy handoff. If you’re curious to see how we grow and would like to play a role in that growth, check out the issues in this GitHub repo! +Backstage Open Source is a newly launched endeavor, and we’re excited to scale +up our design practices! With that said, we’ll be working closely with you, the +community, and iterating and experimenting as we go to see what works best. As a +continual work in progress, we aspire to release early and often. Not only that, +we are committed to working with developers to create a seamless and easy +handoff. If you’re curious to see how we grow and would like to play a role in +that growth, check out the issues in this GitHub repo! ### Collaborative -The Backstage Design Team is small but mighty, and we truly cherish the amazing opportunity we have to work with the Backstage Open Source community! Have an idea? A component request? Feel free to communicate with us via [Discord](https://discord.gg/EBHEGzX) (*#design* channel). Collaboration trumps individual speed, and we want to work with you to make Backstage work for all of our users. +The Backstage Design Team is small but mighty, and we truly cherish the amazing +opportunity we have to work with the Backstage Open Source community! Have an +idea? A component request? Feel free to communicate with us via +[Discord](https://discord.gg/EBHEGzX) (_#design_ channel). Collaboration trumps +individual speed, and we want to work with you to make Backstage work for all of +our users. ### Transparent -There are a lot of exciting things coming up and we want to keep you in the loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll also be posting updates in the *#design* channel on [Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you informed on the decisions we’ve made and why we’ve made them. +There are a lot of exciting things coming up and we want to keep you in the +loop! Keep an eye on our Milestones in GitHub to see where we’re headed. We’ll +also be posting updates in the _#design_ channel on +[Discord](https://discord.gg/EBHEGzX). Not only that, we want to keep you +informed on the decisions we’ve made and why we’ve made them. ## 🛠 Our Practice -The chart below details how we work. ***Stay tuned***: We are currently in the process of securing a Figma workspace for Backstage Open Source, and we plan on referencing Figma documents to share specs and prototypes with the community. + +The chart below details how we work. **_Stay tuned_**: We are currently in the +process of securing a Figma workspace for Backstage Open Source, and we plan on +referencing Figma documents to share specs and prototypes with the community. ### Creating a New Design Component -| Step 1 | Step 2 | Step 3 | Step 4 | Step 5 | Step 6 | -|:---|:---|:---|:---|:---|:---| -| Platform design team submits an issue to **spotify/Backstage GitHub** with a potential component. | Backstage community offers feedback or approval on **spotify/Backstage GitHub**. | Platform design team adjusts accordingly (as they see fit) and update the Figma DLS document. | Designed component is added to **spotify/Backstage GitHub** as an issue. | External or internal Backstage open source contributors build the component. | External or internal contributors add the component to the **Backstage Storybook**. 🎉 | +| Step 1 | Step 2 | Step 3 | Step 4 | Step 5 | Step 6 | +| :------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | +| Platform design team submits an issue to **spotify/Backstage GitHub** with a potential component. | Backstage community offers feedback or approval on **spotify/Backstage GitHub**. | Platform design team adjusts accordingly (as they see fit) and update the Figma DLS document. | Designed component is added to **spotify/Backstage GitHub** as an issue. | External or internal Backstage open source contributors build the component. | External or internal contributors add the component to the **Backstage Storybook**. 🎉 | ### Building for Backstage -| Step 1 | Step 2 | Step 3 | Step 4 | -|:---|:---|:---|:---| -| External or internal contributors use Backstage and come up with an idea of an entity to build for Backstage. |External or internal contributors refer to the Backstage Open Source design system documentation in the Figma DLS document. | External or internal contributors leverage the components and tokens from the Backstage Storybook. | External or internal contributors build their Backstage entity. | -| Step 5 | Step 6 | Step 7 | Step 8 | -|:---|:---|:---|:---| -| External or internal contributors make a pull request for their entity on spotify/Backstage GitHub for review. | Platform designers and devs review the entity and submit feedback or approval on spotify/Backstage GitHub. | External or internal contributors make the changes, pull request is approved and the entity is merged. It’s live on Backstage! 🎉 | If the entity happens to be or include a UX component, it’s added to Backstage Storybook as well. | +| Step 1 | Step 2 | Step 3 | Step 4 | +| :------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | +| External or internal contributors use Backstage and come up with an idea of an entity to build for Backstage. | External or internal contributors refer to the Backstage Open Source design system documentation in the Figma DLS document. | External or internal contributors leverage the components and tokens from the Backstage Storybook. | External or internal contributors build their Backstage entity. | +| Step 5 | Step 6 | Step 7 | Step 8 | +| :------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | +| External or internal contributors make a pull request for their entity on spotify/Backstage GitHub for review. | Platform designers and devs review the entity and submit feedback or approval on spotify/Backstage GitHub. | External or internal contributors make the changes, pull request is approved and the entity is merged. It’s live on Backstage! 🎉 | If the entity happens to be or include a UX component, it’s added to Backstage Storybook as well. | -The following diagram shows the relationship between the Backstage Design System and our foundation, which comprises of [Material UI](https://material-ui.com/) that is shaped by user experience and user interface decisions made by our Backstage Design Team. Also note, we encourage you to take the core experience we’ve crafted and add custom theming to better represent your organization! +The following diagram shows the relationship between the Backstage Design System +and our foundation, which comprises of [Material UI](https://material-ui.com/) +that is shaped by user experience and user interface decisions made by our +Backstage Design Team. Also note, we encourage you to take the core experience +we’ve crafted and add custom theming to better represent your organization! ![dls](DLS.png) - ## ✅ Our Priorities + ### Backstage Design System -This is the set of building blocks for Backstage contributors to leverage as they create rad plugins for Backstage! Why reinvent the wheel when you can use components that have already been vetted by our team and the Backstage community? In the spirit of crafting a cohesive and consistent user experience across all of Backstage, we strongly urge all plugin developers to utilize our Storybook as a reference. Our design system is new and evolving, and we’ll be building it up with your help! + +This is the set of building blocks for Backstage contributors to leverage as +they create rad plugins for Backstage! Why reinvent the wheel when you can use +components that have already been vetted by our team and the Backstage +community? In the spirit of crafting a cohesive and consistent user experience +across all of Backstage, we strongly urge all plugin developers to utilize our +Storybook as a reference. Our design system is new and evolving, and we’ll be +building it up with your help! + ### Core Backstage User Experience -This is the universal user experience that is shared amongst all Backstage users. From more concrete aspects like the plugins marketplace to more abstract ones like end-to-end workflows on Backstage, we’ll be working with the community to create a core user experience that best serves you and your organization. + +This is the universal user experience that is shared amongst all Backstage +users. From more concrete aspects like the plugins marketplace to more abstract +ones like end-to-end workflows on Backstage, we’ll be working with the community +to create a core user experience that best serves you and your organization. ## ⭐️ How to Contribute -### Pick up an issue! -In the beginning, most of our issues will be centered around creating universal components for our Backstage Design System and adding them to our Storybook so plugin developers can reference them. We’ll also be creating issues that are focused on building up our core Backstage user experience. We’ll be labeling our issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and tackle the tasks that interest you. If you have any questions regarding an issue, you can ask them in the comments section of the issue or on [Discord](https://discord.gg/EBHEGzX). We absolutely adore our external contributors and will send you virtual semlas for your contributions! + +### Pick up an issue! + +In the beginning, most of our issues will be centered around creating universal +components for our Backstage Design System and adding them to our Storybook so +plugin developers can reference them. We’ll also be creating issues that are +focused on building up our core Backstage user experience. We’ll be labeling our +issues in GitHub with ‘design’ and/or ‘storybook’ - so feel free to browse and +tackle the tasks that interest you. If you have any questions regarding an +issue, you can ask them in the comments section of the issue or on +[Discord](https://discord.gg/EBHEGzX). We absolutely adore our external +contributors and will send you virtual semlas for your contributions! ### Request a component. -Create an issue (label it design and assign it to katz95) or send us a message on [Discord](https://discord.gg/EBHEGzX) (*#design* channel) with details of what the component is and its relevant use cases. Your request will be reviewed by our design team and you should hear back from us within 1-2 business days. We’ll get back to you and let you know whether your requested component will get picked up by our team as something to be added to our design system. + +Create an issue (label it design and assign it to katz95) or send us a message +on [Discord](https://discord.gg/EBHEGzX) (_#design_ channel) with details of +what the component is and its relevant use cases. Your request will be reviewed +by our design team and you should hear back from us within 1-2 business days. +We’ll get back to you and let you know whether your requested component will get +picked up by our team as something to be added to our design system. ## ✏️ Resources -**[Storybook](http://storybook.backstage.io/)** - where you can view our components. If you’d like to help build up our design system, you can also add components we’ve designed to the Storybook as well. -**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be directed to the *#design* channel. +**[Storybook](http://storybook.backstage.io/)** - where you can view our +components. If you’d like to help build up our design system, you can also add +components we’ve designed to the Storybook as well. + +**[Discord](https://discord.gg/EBHEGzX)** - all design questions should be +directed to the _#design_ channel. **Documentation** + - Patterns (stay tuned) - Figma files/libraries (stay tuned) ## 🔮 Future -### Contributions from designers -Are you a designer at an organisation that’s implementing Backstage? A designer who’s fascinated by the developer productivity problem space? A designer who’s curious about open source design? We’d love for you to contribute. Behind the scenes, we’re setting up a few foundational elements to make sure that contributing to Backstage as a designer is easy. From styling guidelines to UX principles to Figma documents, we’ll make sure you’re equipped to chip in on this project. We’re excited to work with you! In the meantime, we’d love to hear from you on [Discord](https://discord.gg/EBHEGzX). +### Contributions from designers + +Are you a designer at an organisation that’s implementing Backstage? A designer +who’s fascinated by the developer productivity problem space? A designer who’s +curious about open source design? We’d love for you to contribute. Behind the +scenes, we’re setting up a few foundational elements to make sure that +contributing to Backstage as a designer is easy. From styling guidelines to UX +principles to Figma documents, we’ll make sure you’re equipped to chip in on +this project. We’re excited to work with you! In the meantime, we’d love to hear +from you on [Discord](https://discord.gg/EBHEGzX). [Back to Docs](../README.md) diff --git a/docs/generate-uml.sh b/docs/generate-uml.sh new file mode 100755 index 0000000000..55117838ea --- /dev/null +++ b/docs/generate-uml.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# This script uses plantuml to generate SVG images from inline plantuml descriptions. +# See ./auth/oauth.md for an example. +# +# You need to have plantuml installed (brew install plantuml, or some other method). +# +# Either call directly to generate diagrams for all markdown files in this directory, +# or add a --watch flag to rebuild SVGs on changes. + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +if [[ "$1" == '--watch' ]]; then + npx --no-install nodemon --ext md --exec './generate-uml.sh' +fi + +grep '@startuml' -rl --include '*.md' . | while read -r file ; do + echo "Generating : $file" + plantuml -tsvg "$file" 2> >(grep -v "CoreText note:") +done diff --git a/docs/getting-started/Plugin development.md b/docs/getting-started/Plugin development.md index 0f578151a2..41918b5de2 100644 --- a/docs/getting-started/Plugin development.md +++ b/docs/getting-started/Plugin development.md @@ -2,10 +2,10 @@ Backstage plugins provide features to a Backstage App. -Each plugin is treated as a self-contained web app and can include almost any type of content. -Plugins all use a common set of platform APIs and reusable UI components. -Plugins can fetch data from external sources using the regular browser APIs or by depending on -external modules to do the work. +Each plugin is treated as a self-contained web app and can include almost any +type of content. Plugins all use a common set of platform APIs and reusable UI +components. Plugins can fetch data from external sources using the regular +browser APIs or by depending on external modules to do the work. - - - + + - Backstage + <%= app.title %> - +