diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 20d3e1bf..c00094b4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ Please see the [Code of Conduct](./CODE_OF_CONDUCT.md).
## Project Scope
-Learn about the [scope](https://pinniped.dev/docs/scope/) of the project.
+See [SCOPE.md](./SCOPE.md) for some guidelines about what we consider in and out of scope for Pinniped.
## Community Meetings
diff --git a/README.md b/README.md
index b4c27431..196569d0 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ install procedure across all types and origins of Kubernetes clusters,
declarative configuration via Kubernetes APIs, enterprise-grade integrations
with IDPs, and distribution-specific integration strategies.
-### Example Use Cases
+### Example use cases
* Your team uses a large enterprise IDP, and has many clusters that they
manage. Pinniped provides:
@@ -39,17 +39,15 @@ The Pinniped Concierge can be configured to hook into the Pinniped Supervisor's
federated credentials, or it can authenticate users directly via external IDP
credentials.
-To learn more, see [architecture](https://pinniped.dev/docs/architecture/).
+To learn more, see [architecture](https://pinniped.dev/docs/background/architecture/).
-
-
-## Trying Pinniped
+## Getting started with Pinniped
Care to kick the tires? It's easy to [install and try Pinniped](https://pinniped.dev/docs/demo/).
-## Community Meetings
+## Community meetings
-Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occuring every first and third Thursday of the month at 9AM PT / 12PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
+Pinniped is better because of our contributors and maintainers. It is because of you that we can bring great software to the community. Please join us during our online community meetings, occurring every first and third Thursday of the month at 9 AM PT / 12 PM PT. Use [this Zoom Link](https://vmware.zoom.us/j/93798188973?pwd=T3pIMWxReEQvcWljNm1admRoZTFSZz09) to attend and add any agenda items you wish to discuss to [the notes document](https://hackmd.io/rd_kVJhjQfOvfAWzK8A3tQ?view). Join our [Google Group](https://groups.google.com/u/1/g/project-pinniped) to receive invites to this meeting.
If the meeting day falls on a US holiday, please consider that occurrence of the meeting to be canceled.
@@ -61,7 +59,7 @@ Got a question, comment, or idea? Please don't hesitate to reach out via the Git
Contributions are welcome. Before contributing, please see the [contributing guide](CONTRIBUTING.md).
-## Reporting Security Vulnerabilities
+## Reporting security vulnerabilities
Please follow the procedure described in [SECURITY.md](SECURITY.md).
diff --git a/SCOPE.md b/SCOPE.md
new file mode 100644
index 00000000..91dff3bd
--- /dev/null
+++ b/SCOPE.md
@@ -0,0 +1,32 @@
+# Project Scope
+
+The Pinniped project is guided by the following principles.
+
+- Pinniped lets you plug any external identity providers into Kubernetes.
+ These integrations follow enterprise-grade security principles.
+- Pinniped is easy to install and use on any Kubernetes cluster via distribution-specific integration mechanisms.
+- Pinniped uses a declarative configuration via Kubernetes APIs.
+- Pinniped provides optimal user experience when authenticating to many clusters at one time.
+- Pinniped provides enterprise-grade security posture via secure defaults and revocable or very short-lived credentials.
+- Where possible, Pinniped will contribute ideas and code to upstream Kubernetes.
+
+When contributing to Pinniped, please consider whether your contribution follows
+these guiding principles.
+
+## Out Of Scope
+
+The following items are out of scope for the Pinniped project.
+
+- Authorization.
+- Standalone identity provider for general use.
+- Machine-to-machine (service) identity.
+- Running outside of Kubernetes.
+
+## Roadmap
+
+See our [open milestones][milestones] and the [`priority/backlog` label][backlog] for an idea about what's next on our roadmap.
+
+For more details on proposing features and bugs, check out our [contributing](./CONTRIBUTING.md) doc.
+
+[milestones]: https://github.com/vmware-tanzu/pinniped/milestones
+[backlog]: https://github.com/vmware-tanzu/pinniped/labels/priority%2Fbacklog
\ No newline at end of file
diff --git a/deploy/concierge/README.md b/deploy/concierge/README.md
index bd65216f..39ce06b2 100644
--- a/deploy/concierge/README.md
+++ b/deploy/concierge/README.md
@@ -1,39 +1,3 @@
-# Deploying
+# Pinniped Concierge Deployment
-## Connecting Pinniped to an Identity Provider
-
-If you would like to try Pinniped, but you don't have a compatible identity provider,
-you can use Pinniped's test identity provider.
-See [deploy/local-user-authenticator/README.md](../../deploy/local-user-authenticator/README.md)
-for details.
-
-## Installing the Latest Version with Default Options
-
-```bash
-kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge.yaml
-```
-
-## Installing a Specific Version with Default Options
-
-Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
-and use it to replace the version number in the URL below.
-
-```bash
-# Replace v0.4.1 with your preferred version in the URL below
-kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-concierge.yaml
-```
-
-## Installing with Custom Options
-
-Creating your own deployment YAML file requires `ytt` from [Carvel](https://carvel.dev/) to template the YAML files
-in the `deploy/concierge` directory.
-Either [install `ytt`](https://get-ytt.io/) or use the [container image from Dockerhub](https://hub.docker.com/r/k14s/image/tags).
-
-1. `git clone` this repo and `git checkout` the release version tag of the release that you would like to deploy.
-1. The configuration options are in [deploy/concierge/values.yml](values.yaml).
- Fill in the values in that file, or override those values using additional `ytt` command-line options in
- the command below. Use the release version tag as the `image_tag` value.
-2. In a terminal, cd to this `deploy/concierge` directory
-3. To generate the final YAML files, run `ytt --file .`
-4. Deploy the generated YAML using your preferred deployment tool, such as `kubectl` or [`kapp`](https://get-kapp.io/).
- For example: `ytt --file . | kapp deploy --yes --app pinniped --diff-changes --file -`
+See [the how-to guide for details](https://pinniped.dev/docs/howto/install-concierge/).
diff --git a/deploy/supervisor/README.md b/deploy/supervisor/README.md
index 4df8f81c..04d84b81 100644
--- a/deploy/supervisor/README.md
+++ b/deploy/supervisor/README.md
@@ -1,184 +1,3 @@
-# Deploying the Pinniped Supervisor
+# Pinniped Supervisor Deployment
-## What is the Pinniped Supervisor?
-
-The Pinniped Supervisor app is a component of the Pinniped OIDC and Cluster Federation solutions.
-It can be deployed when those features are needed.
-
-## Installing the Latest Version with Default Options
-
-```bash
-kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-supervisor.yaml
-```
-
-## Installing a Specific Version with Default Options
-
-Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number
-and use it to replace the version number in the URL below.
-
-```bash
-# Replace v0.4.1 with your preferred version in the URL below
-kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-supervisor.yaml
-```
-
-## Installing with Custom Options
-
-Creating your own deployment YAML file requires `ytt` from [Carvel](https://carvel.dev/) to template the YAML files
-in the `deploy/supervisor` directory.
-Either [install `ytt`](https://get-ytt.io/) or use the [container image from Dockerhub](https://hub.docker.com/r/k14s/image/tags).
-
-1. `git clone` this repo and `git checkout` the release version tag of the release that you would like to deploy.
-1. The configuration options are in [deploy/supervisor/values.yml](values.yaml).
- Fill in the values in that file, or override those values using additional `ytt` command-line options in
- the command below. Use the release version tag as the `image_tag` value.
-2. In a terminal, cd to this `deploy/supervisor` directory
-3. To generate the final YAML files, run `ytt --file .`
-4. Deploy the generated YAML using your preferred deployment tool, such as `kubectl` or [`kapp`](https://get-kapp.io/).
- For example: `ytt --file . | kapp deploy --yes --app pinniped-supervisor --diff-changes --file -`
-
-## Configuring After Installing
-
-### Exposing the Supervisor App as a Service
-
-The Supervisor app's endpoints should be exposed as HTTPS endpoints with proper TLS certificates signed by a
-Certificate Authority which will be trusted by your user's web browsers. Because there are
-many ways to expose TLS services from a Kubernetes cluster, the Supervisor app leaves this up to the user.
-The most common ways are:
-
-1. Define an [`Ingress` resource](https://kubernetes.io/docs/concepts/services-networking/ingress/) with TLS certificates.
- In this case, the ingress will terminate TLS. Typically, the ingress will then talk plain HTTP to its backend,
- which would be a NodePort or LoadBalancer Service in front of the HTTP port 8080 of the Supervisor pods.
-
- The required configuration of the Ingress is specific to your cluster's Ingress Controller, so please refer to the
- documentation from your Kubernetes provider. If you are using a cluster from a cloud provider, then you'll probably
- want to start with that provider's documentation. For example, if your cluster is a Google GKE cluster, refer to
- the [GKE documentation for Ingress](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress).
- Otherwise, the Kubernetes documentation provides a list of popular
- [Ingress Controllers](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/), including
- [Contour](https://projectcontour.io/) and many others.
-
-1. Or, define a [TCP LoadBalancer Service](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer)
- which is a layer 4 load balancer and does not terminate TLS. In this case, the Supervisor app will need to be
- configured with TLS certificates and will terminate the TLS connection itself (see the section about FederationDomain
- below). The LoadBalancer Service should be configured to use the HTTPS port 443 of the Supervisor pods as its `targetPort`.
-
- *Warning:* Do not expose the Supervisor's port 8080 to the public. It would not be secure for the OIDC protocol
- to use HTTP, because the user's secret OIDC tokens would be transmitted across the network without encryption.
-
-1. Or, expose the Supervisor app using a Kubernetes service mesh technology, e.g. [Istio](https://istio.io/).
- Please see the documentation for your service mesh. Generally, the setup would be similar to the description
- above for defining an ingress, expect the service mesh would probably provide both the ingress with TLS termination
- and the service.
-
-For either of the first two options mentioned above, if you installed using `ytt` then you can use
-the related `service_*` options from [deploy/supervisor/values.yml](values.yaml) to create a Service.
-If you installed using `install-supervisor.yaml` then you can create
-the Service separately after installing the Supervisor app. There is no `Ingress` included in the `ytt` templates,
-so if you choose to use an Ingress then you'll need to create that separately after installing the Supervisor app.
-
-#### Example: Using a LoadBalancer Service
-
-This is an example of creating a LoadBalancer Service to expose port 8443 of the Supervisor app outside the cluster.
-
-```yaml
-apiVersion: v1
-kind: Service
-metadata:
- name: pinniped-supervisor-loadbalancer
- # Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
- namespace: pinniped-supervisor
-spec:
- type: LoadBalancer
- selector:
- # Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
- app: pinniped-supervisor
- ports:
- - protocol: TCP
- port: 443
- targetPort: 8443
-```
-
-#### Example: Using a NodePort Service
-
-A NodePort Service exposes the app as a port on the nodes of the cluster.
-
-This is convenient for use with kind clusters, because kind can
-[expose node ports as localhost ports on the host machine](https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings)
-without requiring an Ingress, although
-[kind also supports several Ingress Controllers](https://kind.sigs.k8s.io/docs/user/ingress).
-
-A NodePort Service could also be used behind an Ingress which is terminating TLS.
-
-For example:
-
-```yaml
-apiVersion: v1
-kind: Service
-metadata:
- name: pinniped-supervisor-nodeport
- # Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
- namespace: pinniped-supervisor
-spec:
- type: NodePort
- selector:
- # Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
- app: pinniped-supervisor
- ports:
- - protocol: TCP
- port: 80
- targetPort: 8080
- nodePort: 31234 # This is the port that you would forward to the kind host. Or omit this key for a random port.
-```
-
-### Configuring the Supervisor to Act as an OIDC Provider
-
-The Supervisor can be configured as an OIDC provider by creating `FederationDomain` resources
-in the same namespace where the Supervisor app was installed. For example:
-
-```yaml
-apiVersion: config.supervisor.pinniped.dev/v1alpha1
-kind: FederationDomain
-metadata:
- name: my-provider
- # Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
- namespace: pinniped-supervisor
-spec:
- # The hostname would typically match the DNS name of the public ingress or load balancer for the cluster.
- # Any path can be specified, which allows a single hostname to have multiple different issuers. The path is optional.
- issuer: https://my-issuer.example.com/any/path
-
- # Optionally configure the name of a Secret in the same namespace, of type `kubernetes.io/tls`,
- # which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider.
- tls:
- secretName: my-tls-cert-secret
-```
-
-#### Configuring TLS for the Supervisor OIDC Endpoints
-
-If you have terminated TLS outside the app, for example using an Ingress with TLS certificates, then you do not need to
-configure TLS certificates on the FederationDomain.
-
-If you are using a LoadBalancer Service to expose the Supervisor app outside your cluster, then you will
-also need to configure the Supervisor app to terminate TLS. There are two places to configure TLS certificates:
-
-1. Each `FederationDomain` can be configured with TLS certificates, using the `spec.tls.secretName` field.
-
-1. The default TLS certificate for all OIDC providers can be configured by creating a Secret called
-`pinniped-supervisor-default-tls-certificate` in the same namespace in which the Supervisor was installed.
-
-The default TLS certificate will be used for all OIDC providers which did not declare a `spec.tls.secretName`.
-Also, the `spec.tls.secretName` will be ignored for incoming requests to the OIDC endpoints
-that use an IP address as the host, so those requests will always present the default TLS certificates
-to the client. When the request includes the hostname, and that hostname matches the hostname of an `Issuer`,
-then the TLS certificate defined by the `spec.tls.secretName` will be used. If that issuer did not
-define `spec.tls.secretName` then the default TLS certificate will be used. If neither exists,
-then the client will get a TLS error because the server will not present any TLS certificate.
-
-It is recommended that you have a DNS entry for your load balancer or Ingress, and that you configure the
-OIDC provider's `Issuer` using that DNS hostname, and that the TLS certificate for that provider also
-covers that same hostname.
-
-You can create the certificate Secrets however you like, for example you could use [cert-manager](https://cert-manager.io/)
-or `kubectl create secret tls`.
-Keep in mind that your users will load some of these endpoints in their web browsers, so the TLS certificates
-should be signed by a Certificate Authority that will be trusted by their browsers.
+See [the how-to guide for details](https://pinniped.dev/docs/howto/install-supervisor/).
diff --git a/go.mod b/go.mod
index 35c4f83d..7547f3cb 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,8 @@ require (
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/coreos/go-oidc/v3 v3.0.0
github.com/davecgh/go-spew v1.1.1
- github.com/go-logr/logr v0.3.0
- github.com/go-logr/stdr v0.2.0
+ github.com/go-logr/logr v0.4.0
+ github.com/go-logr/stdr v0.4.0
github.com/go-openapi/spec v0.19.9
github.com/gofrs/flock v0.8.0
github.com/golang/mock v1.4.4
@@ -24,16 +24,15 @@ require (
github.com/pkg/errors v0.9.1
github.com/sclevine/agouti v3.0.0+incompatible
github.com/sclevine/spec v1.4.0
- github.com/spf13/cobra v1.1.1
+ github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
- github.com/stretchr/testify v1.6.1
+ github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/square/go-jose.v2 v2.5.1
- gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
k8s.io/api v0.20.4
k8s.io/apimachinery v0.20.4
@@ -41,7 +40,7 @@ require (
k8s.io/client-go v0.20.4
k8s.io/component-base v0.20.4
k8s.io/gengo v0.0.0-20201113003025-83324d819ded
- k8s.io/klog/v2 v2.4.0
+ k8s.io/klog/v2 v2.5.0
k8s.io/kube-aggregator v0.20.4
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
diff --git a/go.sum b/go.sum
index cae1b25a..a8bc54aa 100644
--- a/go.sum
+++ b/go.sum
@@ -209,10 +209,10 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs=
-github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
-github.com/go-logr/stdr v0.2.0 h1:EuTFw3BCZ6H/+1VNFlOLVK/sPKwmGMLx8/FTOFWuXpU=
-github.com/go-logr/stdr v0.2.0/go.mod h1:NO1vneyJDqKVgJYnxhwXWWmQPOvNM391IG3H8ql3jiA=
+github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
+github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/stdr v0.4.0 h1:ijk9G/xzDRZdMU1QRhLYdHuWvNZWqte+NZMOGsiKWbc=
+github.com/go-logr/stdr v0.4.0/go.mod h1:NO1vneyJDqKVgJYnxhwXWWmQPOvNM391IG3H8ql3jiA=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@@ -961,8 +961,9 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
-github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
+github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
+github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@@ -987,8 +988,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.1.1/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -1515,8 +1517,9 @@ k8s.io/gengo v0.0.0-20201113003025-83324d819ded h1:JApXBKYyB7l9xx+DK7/+mFjC7A9Bt
k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
+k8s.io/klog/v2 v2.5.0 h1:8mOnjf1RmUPW6KRqQCfYSZq/K20Unmp3IhuZUhxl8KI=
+k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/kube-aggregator v0.20.4 h1:j/SUwPy1eO+ud3XOUGmH18gISPyerqhXOoNRZDbv3fs=
k8s.io/kube-aggregator v0.20.4/go.mod h1:0ixQ9De7KXyHteXizS6nVtrnKqGa4kiuxl9rEBsNccw=
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c=
diff --git a/internal/controller/supervisorconfig/upstreamwatcher/upstreamwatcher_test.go b/internal/controller/supervisorconfig/upstreamwatcher/upstreamwatcher_test.go
index 32ccf198..cba27c39 100644
--- a/internal/controller/supervisorconfig/upstreamwatcher/upstreamwatcher_test.go
+++ b/internal/controller/supervisorconfig/upstreamwatcher/upstreamwatcher_test.go
@@ -152,7 +152,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="secret \"test-client-secret\" not found" "reason"="SecretNotFound" "status"="False" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="secret \"test-client-secret\" not found" "name"="test-name" "namespace"="test-namespace" "reason"="SecretNotFound" "type"="ClientCredentialsValid"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="secret \"test-client-secret\" not found" "name"="test-name" "namespace"="test-namespace" "reason"="SecretNotFound" "type"="ClientCredentialsValid"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@@ -198,7 +198,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "reason"="SecretWrongType" "status"="False" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "name"="test-name" "namespace"="test-namespace" "reason"="SecretWrongType" "type"="ClientCredentialsValid"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" has wrong type \"some-other-type\" (should be \"secrets.pinniped.dev/oidc-client\")" "name"="test-name" "namespace"="test-namespace" "reason"="SecretWrongType" "type"="ClientCredentialsValid"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@@ -243,7 +243,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "reason"="SecretMissingKeys" "status"="False" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="discovered issuer configuration" "reason"="Success" "status"="True" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "name"="test-name" "namespace"="test-namespace" "reason"="SecretMissingKeys" "type"="ClientCredentialsValid"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="referenced Secret \"test-client-secret\" is missing required keys [\"clientID\" \"clientSecret\"]" "name"="test-name" "namespace"="test-namespace" "reason"="SecretMissingKeys" "type"="ClientCredentialsValid"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@@ -291,7 +291,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: illegal base64 data at input byte 7" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@@ -339,7 +339,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="spec.certificateAuthorityData is invalid: no certificates found" "reason"="InvalidTLSConfig" "status"="False" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="spec.certificateAuthorityData is invalid: no certificates found" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="spec.certificateAuthorityData is invalid: no certificates found" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidTLSConfig" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@@ -384,7 +384,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to perform OIDC discovery against \"invalid-url\"" "reason"="Unreachable" "status"="False" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="failed to perform OIDC discovery against \"invalid-url\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to perform OIDC discovery against \"invalid-url\"" "name"="test-name" "namespace"="test-namespace" "reason"="Unreachable" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@@ -430,7 +430,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="failed to parse authorization endpoint URL: parse \"%\": invalid URL escape \"%\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
@@ -476,7 +476,7 @@ func TestController(t *testing.T) {
wantLogs: []string{
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="loaded client credentials" "reason"="Success" "status"="True" "type"="ClientCredentialsValid"`,
`upstream-observer "level"=0 "msg"="updated condition" "name"="test-name" "namespace"="test-namespace" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "reason"="InvalidResponse" "status"="False" "type"="OIDCDiscoverySucceeded"`,
- `upstream-observer "error"="OIDCIdentityProvider has a failing condition" "msg"="found failing condition" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
+ `upstream-observer "msg"="found failing condition" "error"="OIDCIdentityProvider has a failing condition" "message"="authorization endpoint URL scheme must be \"https\", not \"http\"" "name"="test-name" "namespace"="test-namespace" "reason"="InvalidResponse" "type"="OIDCDiscoverySucceeded"`,
},
wantResultingCache: []provider.UpstreamOIDCIdentityProviderI{},
wantResultingUpstreams: []v1alpha1.OIDCIdentityProvider{{
diff --git a/site/config.yaml b/site/config.yaml
index 963c79fa..406a5703 100644
--- a/site/config.yaml
+++ b/site/config.yaml
@@ -22,17 +22,13 @@ markup:
noClasses: false
style: monokailight
tabWidth: 4
-menu:
- docs:
- - name: Overview
- url: /docs/
- weight: 100
- - name: Install
- url: /docs/install/
- weight: 110
- - name: Architecture
- url: /docs/architecture/
- - name: Demo
- url: /docs/demo/
- - name: Scope
- url: /docs/scope/
+
+related:
+ includeNewer: true
+ indices:
+ - name: tags
+ weight: 50
+ - name: date
+ weight: 50
+ threshold: 0
+ toLower: true
\ No newline at end of file
diff --git a/site/content/community/_index.html b/site/content/community/_index.html
index 5726b819..04bc61c7 100644
--- a/site/content/community/_index.html
+++ b/site/content/community/_index.html
@@ -16,8 +16,8 @@ layout: section
}}">
-
-
Head over to our git repo and check out the discussions and issues sections.
+
+
Head over to our GitHub repo and check out the discussions and issues.
@@ -35,7 +35,7 @@ layout: section
-
Pinniped Community Meetings are held every 1st and 3rd Thursday of the month at 9AM PT / 12PM ET
+
Pinniped Community Meetings are held every first and third Thursday of the month at 9 AM PT / 12 PM ET
Join our Google Group to receive invites to the meeting
Watch previous community meetings on our YouTube playlist
diff --git a/site/content/docs/_index.md b/site/content/docs/_index.md
index faa092cb..704c0d52 100644
--- a/site/content/docs/_index.md
+++ b/site/content/docs/_index.md
@@ -1,61 +1,31 @@
---
-title: "Pinniped Documentation"
cascade:
layout: docs
+menu:
+ docs:
+ name: Overview
+ weight: 1
---
-![Pinniped Logo](/docs/img/pinniped_logo.svg)
+# Getting started with Pinniped
-## Overview
+Pinniped is an authentication service for Kubernetes clusters.
+As a Kubernetes cluster administrator or user, you can learn how Pinniped works, see how to use it on your clusters, and dive into internals of Pinniped's APIs and architecture.
-Pinniped provides identity services to Kubernetes.
+Have a question, comment, or idea? Please reach out via [GitHub Discussions](https://github.com/vmware-tanzu/pinniped/discussions) or [join the Pinniped community meetings]({{< ref "/community" >}}).
-Pinniped allows cluster administrators to easily plug in external identity
-providers (IDPs) into Kubernetes clusters. This is achieved via a uniform
-install procedure across all types and origins of Kubernetes clusters,
-declarative configuration via Kubernetes APIs, enterprise-grade integrations
-with IDPs, and distribution-specific integration strategies.
+## Tutorials
-### Example Use Cases
+{{< docsmenu "tutorials" >}}
-* Your team uses a large enterprise IDP, and has many clusters that they
- manage. Pinniped provides:
- * Seamless and robust integration with the IDP
- * Easy installation across clusters of any type and origin
- * A simplified login flow across all clusters
-* Your team shares a single cluster. Pinniped provides:
- * Simple configuration to integrate an IDP
- * Individual, revocable identities
+## How-to guides
-### Architecture
+{{< docsmenu "howtos" >}}
-Pinniped offers credential exchange to enable a user to exchange an external IDP
-credential for a short-lived, cluster-specific credential. Pinniped supports various
-IDP types and implements different integration strategies for various Kubernetes
-distributions to make authentication possible.
+## Reference
-To learn more, see [docs/architecture](/docs/architecture).
+{{< docsmenu "reference" >}}
-
+## Background
-## Trying Pinniped
-
-Care to kick the tires? It's easy to [install and try Pinniped](/docs/demo).
-
-## Discussion
-
-Got a question, comment, or idea? Please don't hesitate to reach out via the GitHub [Discussions](https://github.com/vmware-tanzu/pinniped/discussions) tab at the top of this page.
-
-## Contributions
-
-Contributions are welcome. Before contributing, please see the [contributing guide](https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md).
-
-## Reporting Security Vulnerabilities
-
-Please follow the procedure described in [SECURITY.md](https://github.com/vmware-tanzu/pinniped/blob/main/SECURITY.md).
-
-## License
-
-Pinniped is open source and licensed under Apache License Version 2.0. See [LICENSE](https://github.com/vmware-tanzu/pinniped/blob/main/LICENSE).
-
-Copyright 2020 the Pinniped contributors. All Rights Reserved.
+{{< docsmenu "background" >}}
diff --git a/site/content/docs/background/_index.md b/site/content/docs/background/_index.md
new file mode 100644
index 00000000..74240e8e
--- /dev/null
+++ b/site/content/docs/background/_index.md
@@ -0,0 +1,13 @@
+---
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Background
+ identifier: background
+ weight: 110
+---
+
+# Pinniped background
+
+{{< docsmenu "background" >}}
diff --git a/site/content/docs/architecture.md b/site/content/docs/background/architecture.md
similarity index 96%
rename from site/content/docs/architecture.md
rename to site/content/docs/background/architecture.md
index 77a94278..5e6469a2 100644
--- a/site/content/docs/architecture.md
+++ b/site/content/docs/background/architecture.md
@@ -1,16 +1,20 @@
---
-title: "Pinniped Architecture"
+title: Architecture
+description: Dive into the overall design and implementation details of Pinniped.
cascade:
layout: docs
+menu:
+ docs:
+ name: Architecture
+ weight: 100
+ parent: background
---
-
-# Architecture
-
The principal purpose of Pinniped is to allow users to access Kubernetes
clusters. Pinniped hopes to enable this access across a wide range of Kubernetes
environments with zero configuration.
Pinniped is composed of two parts.
+
1. The Pinniped Supervisor is an OIDC server which allows users to authenticate
with an external identity provider (IDP), and then issues its own federation ID tokens
to be passed on to clusters based on the user information from the IDP.
@@ -24,14 +28,6 @@ understood by the host Kubernetes cluster.
Pinniped supports various authenticator types and OIDC identity providers and implements different integration strategies
for various Kubernetes distributions to make authentication possible.
-## Supported Kubernetes Cluster Types
-
-Pinniped supports the following types of Kubernetes clusters:
-
-- Clusters where the Kube Controller Manager pod is accessible from Pinniped's pods.
-
-Support for other types of Kubernetes distributions is coming soon.
-
## External Identity Provider Integrations
The Pinniped Supervisor will federate identity from one or more IDPs.
diff --git a/site/content/docs/demo.md b/site/content/docs/demo.md
deleted file mode 100644
index 0e4be526..00000000
--- a/site/content/docs/demo.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-title: "Pinniped Demo"
-cascade:
- layout: docs
----
-
-# Trying Pinniped
-1. [Concierge with webhook demo](/docs/concierge-only-demo)
-1. [Concierge with Supervisor and JWT authenticator demo](/docs/concierge-and-supervisor-demo)
diff --git a/site/content/docs/howto/_index.md b/site/content/docs/howto/_index.md
new file mode 100644
index 00000000..d53105e6
--- /dev/null
+++ b/site/content/docs/howto/_index.md
@@ -0,0 +1,14 @@
+---
+title: Pinniped How-To Guides
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: How-to Guides
+ identifier: howtos
+ weight: 50
+---
+
+These how-to guides show you how to install and configure the Pinniped command-line tool, Concierge, and Supervisor:
+
+{{< docsmenu "howtos" >}}
diff --git a/site/content/docs/howto/configure-concierge-jwt.md b/site/content/docs/howto/configure-concierge-jwt.md
new file mode 100644
index 00000000..36386de4
--- /dev/null
+++ b/site/content/docs/howto/configure-concierge-jwt.md
@@ -0,0 +1,142 @@
+---
+title: Configure the Pinniped Concierge to validate JWT tokens
+description: Set up JSON Web Token (JWT) based token authentication on an individual Kubernetes cluster.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Configure Concierge JWT Authentication
+ weight: 25
+ parent: howtos
+---
+The Concierge can validate [JSON Web Tokens (JWTs)](https://tools.ietf.org/html/rfc7519), which are commonly issued by [OpenID Connect (OIDC)](https://openid.net/connect/) identity providers.
+
+This guide shows you how to use this capability _without_ the Pinniped Supervisor.
+This is most useful if you have only a single cluster and want to authenticate to it via an existing OIDC provider.
+
+If you have multiple clusters, you may want to [install]({{< ref "install-supervisor" >}}) and [configure]({{< ref "configure-supervisor" >}}) the Pinniped Supervisor.
+
+## Prerequisites
+
+Before starting, you should have the [command-line tool installed]({{< ref "install-cli" >}}) locally and [Concierge running in your cluster]({{< ref "install-concierge" >}}).
+
+You should also have some existing OIDC issuer configuration:
+
+- An OIDC provider that supports [discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) and the `email` scope.
+- A public client with callback URI `http://127.0.0.1:12345/callback` and `email` scope.
+
+## Create a JWTAuthenticator
+
+Create a JWTAuthenticator describing how to validate tokens from your OIDC issuer:
+
+```yaml
+apiVersion: authentication.concierge.pinniped.dev/v1alpha1
+kind: JWTAuthenticator
+metadata:
+ name: my-jwt-authenticator
+spec:
+ issuer: https://my-issuer.example.com/any/path
+ audience: my-client-id
+ claims:
+ username: email
+```
+
+If you've saved this into a file `my-jwt-authenticator.yaml`, then install it into your cluster using:
+
+```sh
+kubectl apply -f my-jwt-authenticator.yaml
+```
+
+## Generate a kubeconfig file
+
+Generate a kubeconfig file to target the JWTAuthenticator:
+
+```sh
+pinniped get kubeconfig \
+ --oidc-client-id my-client-id \
+ --oidc-scopes openid,email \
+ --oidc-listen-port 12345 \
+ > my-cluster.yaml
+```
+
+This creates a kubeconfig YAML file `my-cluster.yaml` that targets your JWTAuthenticator using `pinniped login oidc` as an [ExecCredential plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins).
+
+It should look something like below:
+
+```yaml
+apiVersion: v1
+kind: Config
+current-context: pinniped
+clusters:
+- cluster:
+ certificate-authority-data: LS0tLS[...]
+ server: https://my-kubernetes-api-endpoint.example.com:59986
+ name: pinniped
+contexts:
+- context:
+ cluster: pinniped
+ user: pinniped
+ name: pinniped
+users:
+- name: pinniped
+ user:
+ exec:
+ apiVersion: client.authentication.k8s.io/v1beta1
+ command: /usr/local/bin/pinniped
+ args:
+ - login
+ - oidc
+ - --enable-concierge
+ - --concierge-api-group-suffix=pinniped.dev
+ - --concierge-authenticator-name=my-jwt-authenticator
+ - --concierge-authenticator-type=jwt
+ - --concierge-endpoint=https://my-kubernetes-api-endpoint.example.com:59986
+ - --concierge-ca-bundle-data=LS0tLS[...]
+ - --issuer=https://my-oidc-issuer.example.com/any/path
+ - --client-id=my-client-id
+ - --scopes=offline_access,openid,email
+ - --listen-port=12345
+ - --request-audience=my-client-id
+```
+
+## Use the kubeconfig file
+
+Use the kubeconfig with `kubectl` to access your cluster:
+
+```sh
+kubectl --kubeconfig my-cluster.yaml get namespaces
+```
+
+You should see:
+
+- The `pinniped login oidc` command is executed automatically by `kubectl`.
+
+- Pinniped opens your browser window and directs you to login with your identity provider.
+
+- After you've logged in, you see a page telling you `you have been logged in and may now close this tab`.
+
+- In your shell, you see your clusters namespaces.
+
+ If instead you get an access denied error, you may need to create a ClusterRoleBinding for the `email` of your OIDC account, for example:
+
+ ```sh
+ kubectl create clusterrolebinding my-user-admin \
+ --clusterrole admin \
+ --user my-username@example.com
+ ```
+
+## Other notes
+
+- Pinniped kubeconfig files do not contain secrets and are safe to share between users.
+
+- Temporary OIDC session credentials such as ID, access, and refresh tokens are stored in:
+ - `~/.config/pinniped/sessions.yaml` (macOS/Linux)
+ - `%USERPROFILE%/.config/pinniped/sessions.yaml` (Windows).
+
+- If your OIDC provider supports [wildcard port number matching](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-16#section-2.1) for localhost URIs, you can omit the `--oidc-listen-port` flag to use a randomly chosen ephemeral TCP port.
+
+- The Pinniped command-line tool can only act as a public client with no client secret.
+ If your provider only supports non-public clients, consider using the Pinniped Supervisor.
+
+- In general, it is not safe to use the same OIDC client across multiple clusters.
+ If you need to access multiple clusters, please [install the Pinniped Supervisor]({{< ref "install-supervisor" >}}).
\ No newline at end of file
diff --git a/site/content/docs/howto/configure-concierge-webhook.md b/site/content/docs/howto/configure-concierge-webhook.md
new file mode 100644
index 00000000..ead557fc
--- /dev/null
+++ b/site/content/docs/howto/configure-concierge-webhook.md
@@ -0,0 +1,116 @@
+---
+title: Configure the Pinniped Concierge to validate webhook tokens
+description: Set up webhook-based token authentication on an individual Kubernetes cluster.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Configure Concierge Webhook Authentication
+ weight: 26
+ parent: howtos
+---
+
+The Concierge can validate arbitrary tokens via an external webhook endpoint using the [same validation process as Kubernetes itself](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication).
+
+## Prerequisites
+
+Before starting, you should have the [command-line tool installed]({{< ref "install-cli" >}}) locally and [Concierge running in your cluster]({{< ref "install-concierge" >}}).
+
+You should also have a custom TokenReview webhook endpoint:
+
+- Your webhook endpoint must handle the `authentication.k8s.io/v1` [TokenReview API](https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/#TokenReview).
+
+- Your webhook must be accessible from the Concierge pod over HTTPS.
+
+## Create a WebhookAuthenticator
+
+Create a WebhookAuthenticator describing how to validate tokens using your webhook:
+
+```yaml
+apiVersion: authentication.concierge.pinniped.dev/v1alpha1
+kind: WebhookAuthenticator
+metadata:
+ name: my-webhook-authenticator
+spec:
+ # HTTPS endpoint to be called as a webhook
+ endpoint: https://my-webhook.example.com/any/path
+ tls:
+ # base64-encoded PEM CA bundle (optional)
+ certificateAuthorityData: "LS0tLS1CRUdJTi[...]"
+```
+
+If you've saved this into a file `my-webhook-authenticator.yaml`, then install it into your cluster using:
+
+```sh
+kubectl apply -f my-webhook-authenticator.yaml
+```
+
+## Generate a kubeconfig file
+
+Generate a kubeconfig file to target the WebhookAuthenticator:
+
+```sh
+pinniped get kubeconfig \
+ --static-token-env MY_CLUSTER_ACCESS_TOKEN \
+ > my-cluster.yaml
+```
+
+This creates a kubeconfig YAML file `my-cluster.yaml` that targets your WebhookAuthenticator using `pinniped login static` as an [ExecCredential plugin](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins).
+
+It should look something like below:
+
+```yaml
+apiVersion: v1
+kind: Config
+current-context: pinniped
+clusters:
+- cluster:
+ certificate-authority-data: LS0tLS[...]
+ server: https://my-kubernetes-api-endpoint.example.com:59986
+ name: pinniped
+contexts:
+- context:
+ cluster: pinniped
+ user: pinniped
+ name: pinniped
+users:
+- name: pinniped
+ user:
+ exec:
+ apiVersion: client.authentication.k8s.io/v1beta1
+ command: /usr/local/bin/pinniped
+ args:
+ - login
+ - oidc
+ - login
+ - static
+ - --enable-concierge
+ - --concierge-api-group-suffix=pinniped.dev
+ - --concierge-authenticator-name=my-webhook-authenticator
+ - --concierge-authenticator-type=webhook
+ - --concierge-endpoint=https://127.0.0.1:59986
+ - --concierge-ca-bundle-data=LS0tLS[...]
+ - --token-env=MY_CLUSTER_ACCESS_TOKEN
+```
+
+## Use the kubeconfig file
+
+Set the `$MY_CLUSTER_ACCESS_TOKEN` environment variable and use the kubeconfig with `kubectl` to access your cluster:
+
+```sh
+MY_CLUSTER_ACCESS_TOKEN=secret-token kubectl --kubeconfig my-cluster.yaml get namespaces
+```
+
+You should see:
+
+- The `pinniped login static` command is silently executed automatically by `kubectl`.
+
+- The command-line tool sends your token to the Concierge which validates it by making a request to your webhook endpoint.
+
+- In your shell, you see your clusters namespaces.
+
+ If instead you get an access denied error, you may need to create a ClusterRoleBinding for the username/groups returned by your webhook, for example:
+
+ ```sh
+ kubectl create clusterrolebinding my-user-admin --clusterrole admin --user my-username
+ ```
diff --git a/site/content/docs/howto/configure-supervisor.md b/site/content/docs/howto/configure-supervisor.md
new file mode 100644
index 00000000..a5ddf342
--- /dev/null
+++ b/site/content/docs/howto/configure-supervisor.md
@@ -0,0 +1,161 @@
+---
+title: Configure the Pinniped Supervisor as an OIDC issuer
+description: Set up the Pinniped Supervisor to provide seamless login flows across multiple clusters.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Configure Supervisor
+ weight: 35
+ parent: howtos
+---
+The Supervisor is an [OpenID Connect (OIDC)](https://openid.net/connect/) issuer that supports connecting a single "upstream" OIDC identity provider to many "downstream" cluster clients.
+
+This guide show you how to use this capability to issue [JSON Web Tokens (JWTs)](https://tools.ietf.org/html/rfc7519) that can be validated by the [Pinniped Concierge]({{< ref "configure-concierge-jwt" >}}).
+
+Before starting, you should have the [command-line tool installed]({{< ref "install-cli" >}}) locally and the Concierge [installed]({{< ref "install-concierge" >}}) in your cluster.
+
+## Expose the Supervisor app as a service
+
+The Supervisor app's endpoints should be exposed as HTTPS endpoints with proper TLS certificates signed by a certificate authority (CA) which is trusted by your user's web browsers.
+
+Because there are many ways to expose TLS services from a Kubernetes cluster, the Supervisor app leaves this up to the user.
+The most common ways are:
+
+1. Define an [`Ingress` resource](https://kubernetes.io/docs/concepts/services-networking/ingress/) with TLS certificates.
+ In this case, the ingress terminates TLS. Typically, the ingress then talks plain HTTP to its backend,
+ which would be a NodePort or LoadBalancer Service in front of the HTTP port 8080 of the Supervisor pods.
+
+ The required configuration of the Ingress is specific to your cluster's Ingress Controller, so please refer to the
+ documentation from your Kubernetes provider. If you are using a cluster from a cloud provider, then you'll probably
+ want to start with that provider's documentation. For example, if your cluster is a Google GKE cluster, refer to
+ the [GKE documentation for Ingress](https://cloud.google.com/kubernetes-engine/docs/concepts/ingress).
+ Otherwise, the Kubernetes documentation provides a list of popular
+ [Ingress Controllers](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/), including
+ [Contour](https://projectcontour.io/) and many others.
+
+1. Or, define a [TCP LoadBalancer Service](https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer)
+ which is a layer 4 load balancer and does not terminate TLS. In this case, the Supervisor app needs to be
+ configured with TLS certificates and terminates the TLS connection itself (see the section about FederationDomain
+ below). The LoadBalancer Service should be configured to use the HTTPS port 443 of the Supervisor pods as its `targetPort`.
+
+ *Warning:* do not expose the Supervisor's port 8080 to the public. It would not be secure for the OIDC protocol
+ to use HTTP, because the user's secret OIDC tokens would be transmitted across the network without encryption.
+
+1. Or, expose the Supervisor app using a Kubernetes service mesh technology, for example [Istio](https://istio.io/).
+ Please see the documentation for your service mesh. Generally, the setup would be similar to the previous description
+ for defining an ingress, except the service mesh would probably provide both the ingress with TLS termination
+ and the service.
+
+For either of the first two options, if you installed using `ytt` then you can use
+the related `service_*` options from [deploy/supervisor/values.yml](values.yaml) to create a Service.
+If you installed using `install-supervisor.yaml` then you can create
+the Service separately after installing the Supervisor app. There is no `Ingress` included in the `ytt` templates,
+so if you choose to use an Ingress then you'll need to create that separately after installing the Supervisor app.
+
+#### Example: Using a LoadBalancer Service
+
+This is an example of creating a LoadBalancer Service to expose port 8443 of the Supervisor app outside the cluster.
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: pinniped-supervisor-loadbalancer
+ # Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
+ namespace: pinniped-supervisor
+spec:
+ type: LoadBalancer
+ selector:
+ # Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
+ app: pinniped-supervisor
+ ports:
+ - protocol: TCP
+ port: 443
+ targetPort: 8443
+```
+
+#### Example: Using a NodePort Service
+
+A NodePort Service exposes the app as a port on the nodes of the cluster.
+
+This is convenient for use with kind clusters, because kind can
+[expose node ports as localhost ports on the host machine](https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings)
+without requiring an Ingress, although
+[kind also supports several Ingress Controllers](https://kind.sigs.k8s.io/docs/user/ingress).
+
+A NodePort Service could also be used behind an Ingress which is terminating TLS.
+
+For example:
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+ name: pinniped-supervisor-nodeport
+ # Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
+ namespace: pinniped-supervisor
+spec:
+ type: NodePort
+ selector:
+ # Assuming that this is how the supervisor pods are labeled. This is the default in install-supervisor.yaml.
+ app: pinniped-supervisor
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 8080
+ nodePort: 31234 # This is the port that you would forward to the kind host. Or omit this key for a random port.
+```
+
+### Configuring the Supervisor to act as an OIDC provider
+
+The Supervisor can be configured as an OIDC provider by creating `FederationDomain` resources
+in the same namespace where the Supervisor app was installed. For example:
+
+```yaml
+apiVersion: config.supervisor.pinniped.dev/v1alpha1
+kind: FederationDomain
+metadata:
+ name: my-provider
+ # Assuming that this is the namespace where the supervisor was installed. This is the default in install-supervisor.yaml.
+ namespace: pinniped-supervisor
+spec:
+ # The hostname would typically match the DNS name of the public ingress or load balancer for the cluster.
+ # Any path can be specified, which allows a single hostname to have multiple different issuers. The path is optional.
+ issuer: https://my-issuer.example.com/any/path
+
+ # Optionally configure the name of a Secret in the same namespace, of type `kubernetes.io/tls`,
+ # which contains the TLS serving certificate for the HTTPS endpoints served by this OIDC Provider.
+ tls:
+ secretName: my-tls-cert-secret
+```
+
+#### Configuring TLS for the Supervisor OIDC endpoints
+
+If you have terminated TLS outside the app, for example using an Ingress with TLS certificates, then you do not need to
+configure TLS certificates on the FederationDomain.
+
+If you are using a LoadBalancer Service to expose the Supervisor app outside your cluster, then you
+also need to configure the Supervisor app to terminate TLS. There are two places to configure TLS certificates:
+
+1. Each `FederationDomain` can be configured with TLS certificates, using the `spec.tls.secretName` field.
+
+1. The default TLS certificate for all OIDC providers can be configured by creating a Secret called
+`pinniped-supervisor-default-tls-certificate` in the same namespace in which the Supervisor was installed.
+
+The default TLS certificate are used for all OIDC providers which did not declare a `spec.tls.secretName`.
+Also, the `spec.tls.secretName` is ignored for incoming requests to the OIDC endpoints
+that use an IP address as the host, so those requests always present the default TLS certificates
+to the client. When the request includes the hostname, and that hostname matches the hostname of an `Issuer`,
+then the TLS certificate defined by the `spec.tls.secretName` is used. If that issuer did not
+define `spec.tls.secretName` then the default TLS certificate is used. If neither exists,
+then the client gets a TLS error because the server does not present any TLS certificate.
+
+It is recommended that you have a DNS entry for your load balancer or Ingress, and that you configure the
+OIDC provider's `Issuer` using that DNS hostname, and that the TLS certificate for that provider also
+covers that same hostname.
+
+You can create the certificate Secrets however you like, for example you could use [cert-manager](https://cert-manager.io/)
+or `kubectl create secret tls`.
+Keep in mind that your users must load some of these endpoints in their web browsers, so the TLS certificates
+should be signed by a certificate authority that is trusted by their browsers.
diff --git a/site/content/docs/howto/install-cli.md b/site/content/docs/howto/install-cli.md
new file mode 100644
index 00000000..8bd71893
--- /dev/null
+++ b/site/content/docs/howto/install-cli.md
@@ -0,0 +1,55 @@
+---
+title: Install the Pinniped command-line tool
+description: Download and set up the `pinniped` command-line tool on macOS, Linux, or Windows clients.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Install CLI
+ weight: 10
+ parent: howtos
+---
+The `pinniped` command-line tool is used to generate Pinniped-compatible kubeconfig files, and is also an important part of the Pinniped-based login flow.
+
+It must be installed by administrators setting up a Pinniped cluster as well as by users accessing a Pinniped-enabled cluster.
+
+## Install using Homebrew on macOS or Linux
+
+Use [Homebrew](https://brew.sh/) to install from the Pinniped [tap](https://github.com/vmware-tanzu/homebrew-pinniped):
+
+- `brew install vmware-tanzu/pinniped/pinniped-cli`
+
+## Download binaries
+
+Find the appropriate binary for your platform from the [latest release](https://github.com/vmware-tanzu/pinniped/releases/latest):
+
+{{< buttonlink href="https://get.pinniped.dev/latest/pinniped-cli-darwin-amd64" >}}Download for macOS/amd64{{< buttonicon "download.png" >}}{{< /buttonlink >}}
+
+{{< buttonlink href="https://get.pinniped.dev/latest/pinniped-cli-linux-amd64" >}}Download for Linux/amd64{{< buttonicon "download.png" >}}{{< /buttonlink >}}
+
+{{< buttonlink href="https://get.pinniped.dev/latest/pinniped-cli-windows-amd64.exe" >}}Download for Windows/amd64{{< buttonicon "download.png" >}}{{< /buttonlink >}}
+
+You should put the command-line tool somewhere on your `$PATH`, such as `/usr/local/bin` on macOS/Linux.
+You'll also need to mark the file as executable.
+
+To find specific versions or view all available platforms and architectures, visit the [releases page](https://github.com/vmware-tanzu/pinniped/releases/).
+
+### Gatekeeper
+
+If you are using macOS, you may get an error dialog when you first run `pinniped` that says `“pinniped” cannot be opened because the developer cannotbe verified`.
+Cancel this dialog, open System Preferences, click Security & Privacy, and click the Allow Anyway button next to the Pinniped message.
+
+Run the command again and another dialog appears saying `macOS cannot verify the developer of “pinniped”. Are you sure you want to open it?`.
+Click Open to allow the command to proceed.
+
+## Install a specific version via script
+
+For example, to install v0.4.1 on Linux/amd64:
+
+```sh
+curl -Lso pinniped https://get.pinniped.dev/v0.4.1/pinniped-cli-linux-amd64 \
+ && chmod +x pinniped \
+ && sudo mv pinniped /usr/local/bin/pinniped
+```
+
+*Next, [install the Concierge]({{< ref "install-concierge.md" >}})!*
diff --git a/site/content/docs/howto/install-concierge.md b/site/content/docs/howto/install-concierge.md
new file mode 100644
index 00000000..e8e66714
--- /dev/null
+++ b/site/content/docs/howto/install-concierge.md
@@ -0,0 +1,60 @@
+---
+title: Install the Pinniped Concierge
+description: Install the Pinniped Concierge service in a Kubernetes cluster.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Install Concierge
+ weight: 20
+ parent: howtos
+---
+This guide shows you how to install the Pinniped Concierge.
+You should have a [supported Kubernetes cluster]({{< ref "../reference/supported-clusters" >}}).
+
+## With default options
+
+1. Install the latest version of the Concierge into the `pinniped-concierge` namespace with default options:
+
+ - `kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge.yaml`
+
+## With specific version and default options
+
+1. Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number and use it to replace the version number in the URL below.
+
+1. Install the Concierge into the `pinniped-concierge` namespace with default options:
+
+ - `kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-concierge.yaml`
+
+ *Replace v0.4.1 with your preferred version number.*
+
+## With custom options
+
+Pinniped uses [ytt](https://carvel.dev/ytt/) from [Carvel](https://carvel.dev/) as a templating system.
+
+1. Install the `ytt` command-line tool using the instructions from the [Carvel documentation](https://carvel.dev/#whole-suite).
+
+1. Clone the Pinniped GitHub repository and visit the `deploy/concierge` directory:
+
+ - `git clone git@github.com:vmware-tanzu/pinniped.git`
+ - `cd pinniped/deploy/concierge`
+
+1. Customize configuration parameters:
+
+ - Edit `values.yaml` with your custom values.
+ - See the [default values](http://github.com/vmware-tanzu/pinniped/tree/main/deploy/concierge/values.yaml) for documentation about individual configuration parameters.
+
+1. Render templated YAML manifests:
+
+ - `ytt --file .`
+
+1. Deploy the templated YAML manifests:
+
+ - *If you're using `kubectl`:*
+
+ `ytt --file . | kubectl apply -f -`
+ - *If you're using [`kapp` from Carvel](https://carvel.dev/kapp/):*
+
+ `ytt --file . | kapp deploy --yes --app pinniped-concierge --diff-changes --file -`
+
+*Next, configure the Concierge for [JWT]({{< ref "configure-concierge-jwt.md" >}}) or [webhook]({{< ref "configure-concierge-webhook.md" >}}) authentication.*
diff --git a/site/content/docs/howto/install-supervisor.md b/site/content/docs/howto/install-supervisor.md
new file mode 100644
index 00000000..a9cea70e
--- /dev/null
+++ b/site/content/docs/howto/install-supervisor.md
@@ -0,0 +1,59 @@
+---
+title: Install the Pinniped Supervisor
+description: Install the Pinniped Supervisor service in a Kubernetes cluster.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Install Supervisor
+ weight: 30
+ parent: howtos
+---
+This guide shows you how to install the Pinniped Supervisor, which allows seamless login across one or many Kubernetes clusters.
+You should have a supported Kubernetes cluster with working HTTPS ingress capabilities.
+
+
+## With default options
+
+1. Install the latest version of the Supervisor into the `pinniped-supervisor` namespace with default options:
+
+ - `kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-supervisor.yaml`
+
+## With specific version and default options
+
+1. Choose your preferred [release](https://github.com/vmware-tanzu/pinniped/releases) version number and use it to replace the version number in the URL below.
+
+1. Install the Supervisor into the `pinniped-supervisor` namespace with default options:
+
+ - `kubectl apply -f https://get.pinniped.dev/v0.4.1/install-pinniped-supervisor.yaml`
+
+ *Replace v0.4.1 with your preferred version number.*
+
+## With custom options
+
+Pinniped uses [ytt](https://carvel.dev/ytt/) from [Carvel](https://carvel.dev/) as a templating system.
+
+1. Install the `ytt` command-line tool using the instructions from the [Carvel documentation](https://carvel.dev/#whole-suite).
+
+1. Clone the Pinniped GitHub repository and visit the `deploy/supervisor` directory:
+
+ - `git clone git@github.com:vmware-tanzu/pinniped.git`
+ - `cd pinniped/deploy/supervisor`
+
+1. Customize configuration parameters:
+
+ - Edit `values.yaml` with your custom values.
+ - See the [default values](http://github.com/vmware-tanzu/pinniped/tree/main/deploy/supervisor/values.yaml) for documentation about individual configuration parameters.
+
+1. Render templated YAML manifests:
+
+ - `ytt --file .`
+
+1. Deploy the templated YAML manifests:
+
+ - *If you're using `kubectl`:*
+
+ `ytt --file . | kubectl apply -f -`
+ - *If you're using [`kapp` from Carvel](https://carvel.dev/kapp/):*
+
+ `ytt --file . | kapp deploy --yes --app pinniped-supervisor --diff-changes --file -`
diff --git a/site/content/docs/install.md b/site/content/docs/install.md
deleted file mode 100644
index 91b9a0e6..00000000
--- a/site/content/docs/install.md
+++ /dev/null
@@ -1,21 +0,0 @@
----
-title: "Installing Pinniped"
-cascade:
- layout: docs
----
-
-# Installing Pinniped
-
-## Install the CLI
-
-- Find the appropriate binary for your platform from the [latest release](https://github.com/vmware-tanzu/pinniped/releases/latest).
-
-- Use Homebrew on macOS: `brew install vmware-tanzu/pinniped/pinniped-cli`.
-
-## Install the Concierge
-
-- See the [concierge deployment guide](https://github.com/vmware-tanzu/pinniped/tree/main/deploy/concierge).
-
-## Install the Supervisor
-
-- See the [supervisor deployment guide](https://github.com/vmware-tanzu/pinniped/tree/main/deploy/supervisor).
\ No newline at end of file
diff --git a/site/content/docs/reference/_index.md b/site/content/docs/reference/_index.md
new file mode 100644
index 00000000..052154c5
--- /dev/null
+++ b/site/content/docs/reference/_index.md
@@ -0,0 +1,13 @@
+---
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Reference
+ identifier: reference
+ weight: 100
+---
+
+# Pinniped reference
+
+{{< docsmenu "reference" >}}
diff --git a/site/content/docs/reference/api.md b/site/content/docs/reference/api.md
new file mode 100644
index 00000000..a27b71a0
--- /dev/null
+++ b/site/content/docs/reference/api.md
@@ -0,0 +1,12 @@
+---
+title: API Types
+description: Reference for the `*.pinniped.dev` Kubernetes API groups.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: API Types
+ weight: 35
+ parent: reference
+---
+Full API reference documentation for the Pinniped Kubernetes API is available [on GitHub](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc).
diff --git a/site/content/docs/reference/cli.md b/site/content/docs/reference/cli.md
new file mode 100644
index 00000000..de1c4e34
--- /dev/null
+++ b/site/content/docs/reference/cli.md
@@ -0,0 +1,84 @@
+---
+title: Command-Line Options Reference
+description: Reference for the `pinniped` command-line tool
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Command-Line Options
+ weight: 30
+ parent: reference
+---
+
+## `pinniped version`
+
+Print the version of this Pinniped CLI.
+
+```sh
+pinniped version [flags]
+```
+
+- `-h`, `--help`:
+
+ help for kubeconfig
+
+## `pinniped get kubeconfig`
+
+Generate a Pinniped-based kubeconfig for a cluster.
+
+```sh
+pinniped get kubeconfig [flags]
+```
+
+- `-h`, `--help`:
+
+ help for kubeconfig
+
+- `--concierge-api-group-suffix string`:
+
+ Concierge API group suffix (default "pinniped.dev")
+- `--concierge-authenticator-name string`:
+
+ Concierge authenticator name (default: autodiscover)
+- `--concierge-authenticator-type string`:
+
+ Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
+- `--kubeconfig string`:
+
+ Path to kubeconfig file
+- `--kubeconfig-context string`:
+
+ Kubeconfig context name (default: current active context)
+- `--no-concierge`:
+
+ Generate a configuration which does not use the concierge, but sends the credential to the cluster directly
+- `--oidc-ca-bundle strings`:
+
+ Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
+- `--oidc-client-id string`:
+
+ OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
+- `--oidc-issuer string`:
+
+ OpenID Connect issuer URL (default: autodiscover)
+- `--oidc-listen-port uint16`:
+
+ TCP port for localhost listener (authorization code flow only)
+- `--oidc-request-audience string`:
+
+ Request a token with an alternate audience using RFC8693 token exchange
+- `--oidc-scopes strings`:
+
+ OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience])
+- `--oidc-session-cache string`:
+
+ Path to OpenID Connect session cache file
+- `--oidc-skip-browser`:
+
+ During OpenID Connect login, skip opening the browser (just print the URL)
+- `--static-token string`:
+
+ Instead of doing an OIDC-based login, specify a static token
+- `--static-token-env string`:
+
+ Instead of doing an OIDC-based login, read a static token from the environment
diff --git a/site/content/docs/reference/supported-clusters.md b/site/content/docs/reference/supported-clusters.md
new file mode 100644
index 00000000..62ff07fe
--- /dev/null
+++ b/site/content/docs/reference/supported-clusters.md
@@ -0,0 +1,29 @@
+---
+title: Supported cluster types
+description: See the supported cluster types for the Pinniped Concierge.
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Supported Cluster Types
+ weight: 10
+ parent: reference
+---
+
+| **Cluster Type** | **Concierge Works?** |
+|-|-|
+| [VMware Tanzu Kubernetes Grid (TKG) clusters](https://tanzu.vmware.com/kubernetes-grid) | Yes |
+| [Kind clusters](https://kind.sigs.k8s.io/) | Yes |
+| [Kubeadm-based clusters](https://kubernetes.io/docs/reference/setup-tools/kubeadm/) | Yes |
+| [Amazon Elastic Kubernetes Service (EKS)](https://aws.amazon.com/eks/) | No |
+| [Google Kubernetes Engine (GKE)](https://cloud.google.com/kubernetes-engine) | No |
+| [Azure Kubernetes Service (AKS)](https://azure.microsoft.com/en-us/overview/kubernetes-on-azure) | No |
+
+## Background
+
+The Pinniped Concierge currently supports clusters where a custom pod can be executed on the same node running `kube-controller-manager`.
+This type of cluster is typically called "self-hosted" because the cluster's control plane is running on nodes that are part of the cluster itself.
+
+In practice, this means that many Kubernetes distributions are supported, but not most managed Kubernetes services
+
+Support for more cluster types, including managed Kubernetes environments, is planned.
diff --git a/site/content/docs/scope.md b/site/content/docs/scope.md
deleted file mode 100644
index 75bcaa19..00000000
--- a/site/content/docs/scope.md
+++ /dev/null
@@ -1,39 +0,0 @@
----
-title: "Pinniped Scope"
-cascade:
- layout: docs
----
-
-
-# Project Scope
-
-The Pinniped project is guided by the following principles.
-* Pinniped lets you plug any external identity providers into
- Kubernetes. These integrations follow enterprise-grade security principles.
-* Pinniped is easy to install and use on any Kubernetes cluster via
- distribution-specific integration mechanisms.
-* Pinniped uses a declarative configuration via Kubernetes APIs.
-* Pinniped provides optimal user experience when authenticating to many
- clusters at one time.
-* Pinniped provides enterprise-grade security posture via secure defaults and
- revocable or very short-lived credentials.
-* Where possible, Pinniped will contribute ideas and code to upstream
- Kubernetes.
-
-When contributing to Pinniped, please consider whether your contribution follows
-these guiding principles.
-
-## Out Of Scope
-
-The following items are out of scope for the Pinniped project.
-* Authorization.
-* Standalone identity provider for general use.
-* Machine-to-machine (service) identity.
-* Running outside of Kubernetes.
-
-## Roadmap
-
-More details coming soon!
-
-For more details on proposing features and bugs, check out our
-[contributing](https://github.com/vmware-tanzu/pinniped/blob/main/CONTRIBUTING.md) doc.
diff --git a/site/content/docs/tutorials/_index.md b/site/content/docs/tutorials/_index.md
new file mode 100644
index 00000000..b95b06cd
--- /dev/null
+++ b/site/content/docs/tutorials/_index.md
@@ -0,0 +1,15 @@
+---
+cascade:
+ layout: docs
+menu:
+ docs:
+ name: Tutorials
+ identifier: tutorials
+ weight: 40
+---
+
+# Pinniped tutorials
+
+These tutorials demonstrate how to use the Pinniped command-line tool, Concierge, and Supervisor:
+
+{{< docsmenu "tutorials" >}}
diff --git a/site/content/docs/concierge-and-supervisor-demo.md b/site/content/docs/tutorials/concierge-and-supervisor-demo.md
similarity index 72%
rename from site/content/docs/concierge-and-supervisor-demo.md
rename to site/content/docs/tutorials/concierge-and-supervisor-demo.md
index 0274c409..382cdfc0 100644
--- a/site/content/docs/concierge-and-supervisor-demo.md
+++ b/site/content/docs/tutorials/concierge-and-supervisor-demo.md
@@ -1,11 +1,14 @@
---
-title: "Pinniped Concierge and Supervisor Demo"
+title: Learn to use the Pinniped Supervisor alongside the Concierge
+description: See how the Pinniped Supervisor streamlines login to multiple Kubernetes clusters.
cascade:
layout: docs
+menu:
+ docs:
+ name: Concierge with Supervisor
+ parent: tutorials
---
-# Trying Pinniped Supervisor and Concierge
-
## Prerequisites
1. A Kubernetes cluster of a type supported by Pinniped Concierge as described in [architecture](/docs/architecture).
@@ -13,18 +16,18 @@ cascade:
Don't have a cluster handy? Consider using [kind](https://kind.sigs.k8s.io/) on your local machine.
See below for an example of using kind.
-1. A Kubernetes cluster of a type supported by Pinniped Supervisor (this can be the same cluster as the above, or different).
+1. A Kubernetes cluster of a type supported by Pinniped Supervisor (this can be the same cluster as the first, or different).
-1. A kubeconfig that has admin-like privileges on each cluster.
+1. A kubeconfig that has administrator-like privileges on each cluster.
1. An external OIDC identity provider to use as the source of identity for Pinniped.
## Overview
-Installing and trying Pinniped on any cluster will consist of the following general steps. See the next section below
+Installing and trying Pinniped on any cluster consists of the following general steps. See the next section below
for a more specific example, including the commands to use for that case.
-1. Install the Pinniped Supervisor. See [deploy/supervisor/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/supervisor/README.md).
+1. [Install the Supervisor]({{< ref "../howto/install-supervisor" >}}).
1. Create a
[`FederationDomain`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomain)
via the installed Pinniped Supervisor.
@@ -35,31 +38,31 @@ for a more specific example, including the commands to use for that case.
1. Create a
[`JWTAuthenticator`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator)
via the installed Pinniped Concierge.
-1. Download the Pinniped CLI from [Pinniped's github Releases page](https://github.com/vmware-tanzu/pinniped/releases/latest).
-1. Generate a kubeconfig using the Pinniped CLI. Run `pinniped get kubeconfig --help` for more information.
-1. Run `kubectl` commands using the generated kubeconfig. The Pinniped Supervisor and Concierge will automatically be used for authentication during those commands.
+1. [Install the Pinniped command-line tool]({{< ref "../howto/install-cli" >}}).
+1. Generate a kubeconfig using the Pinniped command-line tool. Run `pinniped get kubeconfig --help` for more information.
+1. Run `kubectl` commands using the generated kubeconfig. The Pinniped Supervisor and Concierge are automatically used for authentication during those commands.
-## Example of Deploying on Multiple kind Clusters
+## Example of deploying on multiple kind clusters
[kind](https://kind.sigs.k8s.io) is a tool for creating and managing Kubernetes clusters on your local machine
-which uses Docker containers as the cluster's "nodes". This is a convenient way to try out Pinniped on local
+which uses Docker containers as the cluster's nodes. This is a convenient way to try out Pinniped on local
non-production clusters.
-The following steps will deploy the latest release of Pinniped on kind. It will deploy the Pinniped
+The following steps deploy the latest release of Pinniped on kind. They deploy the Pinniped
Supervisor on one cluster, and the Pinniped Concierge on another cluster. A multi-cluster deployment
-strategy is typical for Pinniped. The Pinniped Concierge will use a
+strategy is typical for Pinniped. The Pinniped Concierge uses a
[`JWTAuthenticator`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator)
to authenticate federated identities from the Supervisor.
1. Install the tools required for the following steps.
- - [Install kind](https://kind.sigs.k8s.io/docs/user/quick-start/), if not already installed. e.g. `brew install kind` on MacOS.
+ - [Install kind](https://kind.sigs.k8s.io/docs/user/quick-start/), if not already installed. For example, `brew install kind` on macOS.
- - kind depends on Docker. If not already installed, [install Docker](https://docs.docker.com/get-docker/), e.g. `brew cask install docker` on MacOS.
+ - kind depends on Docker. If not already installed, [install Docker](https://docs.docker.com/get-docker/), for example `brew cask install docker` on macOS.
- This demo requires `kubectl`, which comes with Docker, or can be [installed separately](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
- - This demo requires `openssl`, which is installed on MacOS by default, or can be [installed separately](https://www.openssl.org/).
+ - This demo requires `openssl`, which is installed on macOS by default, or can be [installed separately](https://www.openssl.org/).
1. Create a new Kubernetes cluster for the Pinniped Supervisor using `kind create cluster --name pinniped-supervisor`.
@@ -68,10 +71,10 @@ to authenticate federated identities from the Supervisor.
1. Deploy the Pinniped Supervisor with a valid serving certificate and network path. See
[deploy/supervisor/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/supervisor/README.md).
- For purposes of this demo, the following issuer will be used. This issuer is specific to DNS and
- TLS infrastructure set up for this demo.
+ For purposes of this demo, the following issuer is used. This issuer is specific to DNS and
+ TLS infrastructure set up for this demo:
- ```bash
+ ```sh
issuer=https://my-supervisor.demo.pinniped.dev
```
@@ -88,7 +91,7 @@ to authenticate federated identities from the Supervisor.
[`FederationDomain`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-supervisor-config-v1alpha1-federationdomain)
object to configure the Pinniped Supervisor to issue federated identities.
- ```bash
+ ```sh
cat <}})
for instructions on how to deploy using `ytt`.
- If you prefer to install a specific version, replace `latest` in the above URL with the version number such as `v0.4.1`.
-
1. Generate a random audience value for this cluster.
- ```bash
+ ```sh
audience="$(openssl rand -hex 8)"
```
@@ -162,7 +163,7 @@ to authenticate federated identities from the Supervisor.
[`JWTAuthenticator`](https://github.com/vmware-tanzu/pinniped/blob/main/generated/1.20/README.adoc#k8s-api-go-pinniped-dev-generated-1-19-apis-concierge-authentication-v1alpha1-jwtauthenticator)
object to configure the Pinniped Concierge to authenticate using the Pinniped Supervisor.
- ```bash
+ ```sh
cat <}}) for more details.
1. Generate a kubeconfig for the current cluster.
- ```bash
+
+ ```sh
pinniped get kubeconfig \
--kubeconfig-context kind-pinniped-concierge \
> /tmp/pinniped-kubeconfig
```
- If you are using MacOS, you may get an error dialog that says
- `“pinniped” cannot be opened because the developer cannot be verified`. Cancel this dialog, open System Preferences,
- click on Security & Privacy, and click the Allow Anyway button next to the Pinniped message.
- Run the above command again and another dialog will appear saying
- `macOS cannot verify the developer of “pinniped”. Are you sure you want to open it?`.
- Click Open to allow the command to proceed.
+1. Try using the generated kubeconfig to issue arbitrary `kubectl` commands. The `pinniped` command-line tool
+ opens a browser page that can be used to login to the external OIDC identity provider configured earlier.
-1. Try using the generated kubeconfig to issue arbitrary `kubectl` commands. The `pinniped` CLI will
- open a browser page that can be used to login to the external OIDC identity provider configured earlier.
-
- ```bash
+ ```sh
kubectl --kubeconfig /tmp/pinniped-kubeconfig get pods -n pinniped-concierge
```
@@ -209,30 +208,28 @@ to authenticate federated identities from the Supervisor.
to the upstream OIDC identity provider. However, this does prove that you are authenticated and
acting as the `pinny` user.
-1. As the admin user, create RBAC rules for the test user to give them permissions to perform actions on the cluster.
+1. As the administrator user, create RBAC rules for the test user to give them permissions to perform actions on the cluster.
For example, grant the test user permission to view all cluster resources.
- ```bash
+ ```sh
kubectl --context kind-pinniped-concierge create clusterrolebinding pinny-can-read --clusterrole view --user pinny
```
1. Use the generated kubeconfig to issue arbitrary `kubectl` commands as the `pinny` user.
- ```bash
+ ```sh
kubectl --kubeconfig /tmp/pinniped-kubeconfig get pods -n pinniped-concierge
```
The user has permission to list pods, so the command succeeds this time.
- Pinniped has provided authentication into the cluster for your `kubectl` command! 🎉
+ Pinniped has provided authentication into the cluster for your `kubectl` command. 🎉
1. Carry on issuing as many `kubectl` commands as you'd like as the `pinny` user.
- Each invocation will use Pinniped for authentication.
+ Each invocation uses Pinniped for authentication.
You may find it convenient to set the `KUBECONFIG` environment variable rather than passing `--kubeconfig` to each invocation.
- ```bash
+ ```sh
export KUBECONFIG=/tmp/pinniped-kubeconfig
kubectl get namespaces
kubectl get pods -A
```
-
-1. Profit! 💰
diff --git a/site/content/docs/concierge-only-demo.md b/site/content/docs/tutorials/concierge-only-demo.md
similarity index 62%
rename from site/content/docs/concierge-only-demo.md
rename to site/content/docs/tutorials/concierge-only-demo.md
index 8ecfa94a..22943c84 100644
--- a/site/content/docs/concierge-only-demo.md
+++ b/site/content/docs/tutorials/concierge-only-demo.md
@@ -1,11 +1,15 @@
---
-title: "Pinniped Concierge Only Demo"
+title: Learn to use the Pinniped Concierge
+description: See how the Pinniped Concierge works to provide a uniform login flow across different Kubernetes clusters.
cascade:
layout: docs
+menu:
+ docs:
+ name: Concierge with Webhook
+ parent: tutorials
+ weight: 100
---
-# Trying Pinniped Concierge
-
## Prerequisites
1. A Kubernetes cluster of a type supported by Pinniped as described in [architecture](/docs/architecture).
@@ -20,48 +24,53 @@ cascade:
by following the directions in [deploy/local-user-authenticator/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/local-user-authenticator/README.md).
See below for an example of deploying this on kind.
-1. A kubeconfig where the current context points to the cluster and has admin-like
+1. A kubeconfig where the current context points to the cluster and has administrator-like
privileges on that cluster.
## Overview
-Installing and trying Pinniped on any cluster will consist of the following general steps. See the next section below
+Installing and trying the Pinniped Concierge on any cluster consists of the following general steps. See the next section below
for a more specific example of installing onto a local kind cluster, including the exact commands to use for that case.
-1. Install the Pinniped Concierge. See [deploy/concierge/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/concierge/README.md).
-1. Download the Pinniped CLI from [Pinniped's github Releases page](https://github.com/vmware-tanzu/pinniped/releases/latest).
-1. Generate a kubeconfig using the Pinniped CLI. Run `pinniped get kubeconfig --help` for more information.
-1. Run `kubectl` commands using the generated kubeconfig. The Pinniped Concierge will automatically be used for authentication during those commands.
+1. [Install the Concierge]({{< ref "../howto/install-concierge" >}}).
+1. [Install the Pinniped command-line tool]({{< ref "../howto/install-cli" >}}).
+1. Configure the Concierge with a
+ [JWT]({{< ref "../howto/configure-concierge-jwt" >}}) or
+ [webhook]({{< ref "../howto/configure-concierge-webhook" >}}) authenticator.
+1. Generate a kubeconfig using the Pinniped command-line tool (run `pinniped get kubeconfig --help` for more information).
+1. Run `kubectl` commands using the generated kubeconfig.
-## Example of Deploying on kind
+ The Pinniped Concierge is automatically be used for authentication during those commands.
+
+## Example of deploying on kind
[kind](https://kind.sigs.k8s.io) is a tool for creating and managing Kubernetes clusters on your local machine
-which uses Docker containers as the cluster's "nodes". This is a convenient way to try out Pinniped on a local
+which uses Docker containers as the cluster's nodes. This is a convenient way to try out Pinniped on a local
non-production cluster.
-The following steps will deploy the latest release of Pinniped on kind using the local-user-authenticator component
+The following steps deploy the latest release of Pinniped on kind using the local-user-authenticator component
as the authenticator.
1. Install the tools required for the following steps.
- - [Install kind](https://kind.sigs.k8s.io/docs/user/quick-start/), if not already installed. e.g. `brew install kind` on MacOS.
+ - [Install kind](https://kind.sigs.k8s.io/docs/user/quick-start/), if not already installed. For example, `brew install kind` on macOS.
- - kind depends on Docker. If not already installed, [install Docker](https://docs.docker.com/get-docker/), e.g. `brew cask install docker` on MacOS.
+ - kind depends on Docker. If not already installed, [install Docker](https://docs.docker.com/get-docker/), for example `brew cask install docker` on macOS.
- This demo requires `kubectl`, which comes with Docker, or can be [installed separately](https://kubernetes.io/docs/tasks/tools/install-kubectl/).
- - This demo requires a tool capable of generating a `bcrypt` hash in order to interact with
+ - This demo requires a tool capable of generating a `bcrypt` hash to interact with
the webhook. The example below uses `htpasswd`, which is installed on most macOS systems, and can be
- installed on some Linux systems via the `apache2-utils` package (e.g., `apt-get install
+ installed on some Linux systems via the `apache2-utils` package (for example, `apt-get install
apache2-utils`).
1. Create a new Kubernetes cluster using `kind create cluster`. Optionally provide a cluster name using the `--name` flag.
- kind will automatically update your kubeconfig to point to the new cluster as a user with admin-like permissions.
+ kind automatically updates your kubeconfig to point to the new cluster as a user with administrator-like permissions.
1. Deploy the local-user-authenticator app. This is a demo authenticator. In production, you would configure
an authenticator that works with your real identity provider, and therefore would not need to deploy or configure local-user-authenticator.
- ```bash
+ ```sh
kubectl apply -f https://get.pinniped.dev/latest/install-local-user-authenticator.yaml
```
@@ -70,11 +79,11 @@ as the authenticator.
see [deploy/local-user-authenticator/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/local-user-authenticator/README.md)
for instructions on how to deploy using `ytt`.
- If you prefer to install a specific version, replace `latest` in the above URL with the version number such as `v0.4.1`.
+ If you prefer to install a specific version, replace `latest` in the URL with the version number such as `v0.4.1`.
1. Create a test user named `pinny-the-seal` in the local-user-authenticator namespace.
- ```bash
+ ```sh
kubectl create secret generic pinny-the-seal \
--namespace local-user-authenticator \
--from-literal=groups=group1,group2 \
@@ -83,7 +92,7 @@ as the authenticator.
1. Fetch the auto-generated CA bundle for the local-user-authenticator's HTTP TLS endpoint.
- ```bash
+ ```sh
kubectl get secret local-user-authenticator-tls-serving-certificate --namespace local-user-authenticator \
-o jsonpath={.data.caCertificate} \
| tee /tmp/local-user-authenticator-ca-base64-encoded
@@ -91,12 +100,12 @@ as the authenticator.
1. Deploy the Pinniped Concierge.
- ```bash
+ ```sh
kubectl apply -f https://get.pinniped.dev/latest/install-pinniped-concierge.yaml
```
The `install-pinniped-concierge.yaml` file includes the default deployment options.
- If you would prefer to customize the available options, please see [deploy/concierge/README.md](https://github.com/vmware-tanzu/pinniped/blob/main/deploy/concierge/README.md)
+ If you would prefer to customize the available options, please see the [Concierge installation guide]({{< ref "../howto/install-concierge" >}})
for instructions on how to deploy using `ytt`.
1. Create a `WebhookAuthenticator` object to configure the Pinniped Concierge to authenticate using local-user-authenticator.
@@ -114,61 +123,63 @@ as the authenticator.
EOF
```
-1. Download the latest version of the Pinniped CLI binary for your platform
- from Pinniped's [latest release](https://github.com/vmware-tanzu/pinniped/releases/latest).
+1. Download the latest version of the Pinniped command-line tool for your platform.
+ On macOS or Linux, you can do this using Homebrew:
-1. Move the Pinniped CLI binary to your preferred filename and directory. Add the executable bit,
- e.g. `chmod +x /usr/local/bin/pinniped`.
-
-1. Generate a kubeconfig for the current cluster. Use `--static-token` to include a token which should
- allow you to authenticate as the user that you created above.
-
- ```bash
- pinniped get kubeconfig --static-token "pinny-the-seal:password123" --concierge-authenticator-type webhook --concierge-authenticator-name local-user-authenticator > /tmp/pinniped-kubeconfig
+ ```sh
+ brew install vmware-tanzu/pinniped/pinniped-cli
```
- If you are using MacOS, you may get an error dialog that says
- `“pinniped” cannot be opened because the developer cannot be verified`. Cancel this dialog, open System Preferences,
- click on Security & Privacy, and click the Allow Anyway button next to the Pinniped message.
- Run the above command again and another dialog will appear saying
- `macOS cannot verify the developer of “pinniped”. Are you sure you want to open it?`.
- Click Open to allow the command to proceed.
+ On other platforms, see the [command-line installation guide]({{< ref "../howto/install-cli" >}}) for more details.
+
+1. Generate a kubeconfig for the current cluster. Use `--static-token` to include a token which should
+ allow you to authenticate as the user that you created previously.
+
+ ```sh
+ pinniped get kubeconfig \
+ --static-token "pinny-the-seal:password123" \
+ --concierge-authenticator-type webhook \
+ --concierge-authenticator-name local-user-authenticator \
+ > /tmp/pinniped-kubeconfig
+ ```
1. Try using the generated kubeconfig to issue arbitrary `kubectl` commands as
the `pinny-the-seal` user.
- ```bash
- kubectl --kubeconfig /tmp/pinniped-kubeconfig get pods -n pinniped-concierge
+ ```sh
+ kubectl --kubeconfig /tmp/pinniped-kubeconfig \
+ get pods -n pinniped-concierge
```
Because this user has no RBAC permissions on this cluster, the previous command
- results in the error `Error from server (Forbidden): pods is forbidden: User "pinny-the-seal" cannot list resource "pods" in API group "" in the namespace "pinniped"`.
+ results in the error `Error from server (Forbidden): pods is forbidden: User "pinny-the-seal" cannot list resource "pods" in API group "" in the namespace "pinniped-concierge"`.
However, this does prove that you are authenticated and acting as the `pinny-the-seal` user.
-1. As the admin user, create RBAC rules for the test user to give them permissions to perform actions on the cluster.
+1. As the administrator user, create RBAC rules for the test user to give them permissions to perform actions on the cluster.
For example, grant the test user permission to view all cluster resources.
- ```bash
- kubectl create clusterrolebinding pinny-can-read --clusterrole view --user pinny-the-seal
+ ```sh
+ kubectl create clusterrolebinding pinny-can-read \
+ --clusterrole view \
+ --user pinny-the-seal
```
1. Use the generated kubeconfig to issue arbitrary `kubectl` commands as the `pinny-the-seal` user.
- ```bash
- kubectl --kubeconfig /tmp/pinniped-kubeconfig get pods -n pinniped-concierge
+ ```sh
+ kubectl --kubeconfig /tmp/pinniped-kubeconfig \
+ get pods -n pinniped-concierge
```
The user has permission to list pods, so the command succeeds this time.
- Pinniped has provided authentication into the cluster for your `kubectl` command! 🎉
+ Pinniped has provided authentication into the cluster for your `kubectl` command. 🎉
1. Carry on issuing as many `kubectl` commands as you'd like as the `pinny-the-seal` user.
- Each invocation will use Pinniped for authentication.
+ Each invocation uses Pinniped for authentication.
You may find it convenient to set the `KUBECONFIG` environment variable rather than passing `--kubeconfig` to each invocation.
- ```bash
+ ```sh
export KUBECONFIG=/tmp/pinniped-kubeconfig
kubectl get namespaces
kubectl get pods -A
```
-
-1. Profit! 💰
diff --git a/site/content/posts/a-seal-of-approval.md b/site/content/posts/a-seal-of-approval.md
index 5e9bb170..de93e6fd 100644
--- a/site/content/posts/a-seal-of-approval.md
+++ b/site/content/posts/a-seal-of-approval.md
@@ -5,7 +5,7 @@ date: 2020-11-12
author: Pablo Schuhmacher
image: /img/logo.svg
excerpt: "Pinniped intends to bring that dream state — log in once and you’re done — to reality."
-tags: ['Pablo Schuhmacher']
+tags: ['Pablo Schuhmacher', 'release']
---
Kubernetes, containers, microservices: They’ve all turned conventional application development wisdom inside out. But for all the wonders introduced and new technologies released, there are still a few things that remain difficult, cumbersome, or just really really frustrating when it comes to Kubernetes. We have set out to make one of those things easier and more understandable: authentication.
diff --git a/site/content/posts/multiple-pinnipeds.md b/site/content/posts/multiple-pinnipeds.md
index e875db13..5ce0a610 100644
--- a/site/content/posts/multiple-pinnipeds.md
+++ b/site/content/posts/multiple-pinnipeds.md
@@ -87,7 +87,7 @@ The default behavior of Pinniped remains unchanged, and we made sure to implemen
### Advantages and Disadvantages
-With v0.5.0, each instance of Pinniped to be upgraded and operated 100% independently, with no coordination or shared state needed.
+With v0.5.0, each instance of Pinniped can be upgraded and operated 100% independently, with no coordination or shared state needed.
One remaining constraint is that each instance should be deployed into its own namespace.
This ensures that any other standard Kubernetes objects such as Secrets and ConfigMaps referenced by the configuration do not overlap.
diff --git a/site/netlify.toml b/site/netlify.toml
index 75cbcf73..3118ada5 100644
--- a/site/netlify.toml
+++ b/site/netlify.toml
@@ -28,3 +28,16 @@ HUGO_VERSION = "0.78.0"
[context.next.environment]
HUGO_ENABLEGITINFO = "true"
+
+[[headers]]
+ for = "/fonts/*"
+ [headers.values]
+ Access-Control-Allow-Origin = "*"
+
+[[headers]]
+ for = "/*"
+ [headers.values]
+ Content-Security-Policy = "default-src 'self'; img-src *"
+ X-Content-Type-Options = "nosniff"
+ X-Frame-Options = "DENY"
+ X-XSS-Protection = "1; mode=block"
\ No newline at end of file
diff --git a/site/redirects/get.pinniped.dev/netlify.toml b/site/redirects/get.pinniped.dev/netlify.toml
index 2618e31f..2e7d89ac 100644
--- a/site/redirects/get.pinniped.dev/netlify.toml
+++ b/site/redirects/get.pinniped.dev/netlify.toml
@@ -1,3 +1,8 @@
+[[redirects]]
+ from = "/"
+ to = "https://pinniped.dev"
+ status = 302
+ force = true
[[redirects]]
from = "/latest/*"
diff --git a/site/resources/_gen/assets/scss/scss/site.scss_8967e03afb92eb0cac064520bf021ba2.content b/site/resources/_gen/assets/scss/scss/site.scss_8967e03afb92eb0cac064520bf021ba2.content
index 1ce27817..7e34aa0e 100644
--- a/site/resources/_gen/assets/scss/scss/site.scss_8967e03afb92eb0cac064520bf021ba2.content
+++ b/site/resources/_gen/assets/scss/scss/site.scss_8967e03afb92eb0cac064520bf021ba2.content
@@ -1,3 +1,3 @@
-body{font-family:"Metropolis-Light",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:36px}h2{font-size:28px}h3{font-size:22px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:"Metropolis-Medium",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}@font-face{font-family:"Metropolis-Bold";src:url("/fonts/Metropolis-Bold.eot");src:url("/fonts/Metropolis-Bold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Bold.woff2") format("woff2"),url("/fonts/Metropolis-Bold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-BoldItalic";src:url("/fonts/Metropolis-BoldItalic.eot");src:url("/fonts/Metropolis-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-BoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-BoldItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Light";src:url("/fonts/Metropolis-Light.eot");src:url("/fonts/Metropolis-Light.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Light.woff2") format("woff2"),url("/fonts/Metropolis-Light.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-LightItalic";src:url("/fonts/Metropolis-LightItalic.eot");src:url("/fonts/Metropolis-LightItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-LightItalic.woff2") format("woff2"),url("/fonts/Metropolis-LightItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Regular";src:url("/fonts/Metropolis-Regular.eot");src:url("/fonts/Metropolis-Regular.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Regular.woff2") format("woff2"),url("/fonts/Metropolis-Regular.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-RegularItalic";src:url("/fonts/Metropolis-RegularItalic.eot");src:url("/fonts/Metropolis-RegularItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-RegularItalic.woff2") format("woff2"),url("/fonts/Metropolis-RegularItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Medium";src:url("/fonts/Metropolis-Medium.eot");src:url("/fonts/Metropolis-Medium.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Medium.woff2") format("woff2"),url("/fonts/Metropolis-Medium.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-MediumItalic";src:url("/fonts/Metropolis-MediumItalic.eot");src:url("/fonts/Metropolis-MediumItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-MediumItalic.woff2") format("woff2"),url("/fonts/Metropolis-MediumItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBold";src:url("/fonts/Metropolis-SemiBold.eot");src:url("/fonts/Metropolis-SemiBold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBold.woff2") format("woff2"),url("/fonts/Metropolis-SemiBold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBoldItalic";src:url("/fonts/Metropolis-SemiBoldItalic.eot");src:url("/fonts/Metropolis-SemiBoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-SemiBoldItalic.woff") format("woff");font-weight:normal;font-style:normal}header .wrapper{padding:10px 20px}header .desktop-links{float:right;margin:15px 0px 0px 0px;padding-left:0px}header a{color:#333;font-family:"Metropolis-Light",Helvetica,sans-serif}header a.active{font-family:"Metropolis-Medium",Helvetica,sans-serif}header li img{vertical-align:bottom;margin-right:10px}header .mobile{display:none}@media only screen and (min-width: 768px) and (max-width: 1279px){header .desktop-links li{padding-right:10px}}@media only screen and (max-width: 767px){header{position:relative}header .expanded-icon{display:none;padding:11px 3px 0px 0px}header .collapsed-icon{padding-top:12px}header .mobile-menu-visible .mobile{display:block}header .mobile-menu-visible .mobile .collapsed-icon{display:none}header .mobile-menu-visible .mobile .expanded-icon{display:block}header .desktop-links{display:none}header .mobile{display:block}header button{float:right}header button:focus{outline:none}header ul{padding-left:0px}header ul li{display:block;margin:20px 0px}header .mobile-menu{position:absolute;background-color:#fff;width:100%;top:70px;left:0px;padding-bottom:20px;display:none}header .mobile-menu .header-links{margin:0px 20px}header .mobile-menu .social{margin:0px 20px;padding-top:20px}header .mobile-menu .social img{vertical-align:middle;padding-right:10px}header .mobile-menu .social a{font-size:14px;padding-right:35px}header .mobile-menu .social a:last-of-type{padding-right:0px}}body{font-family:"Metropolis-Light",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:36px}h2{font-size:28px}h3{font-size:22px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:"Metropolis-Medium",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}@font-face{font-family:"Metropolis-Bold";src:url("/fonts/Metropolis-Bold.eot");src:url("/fonts/Metropolis-Bold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Bold.woff2") format("woff2"),url("/fonts/Metropolis-Bold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-BoldItalic";src:url("/fonts/Metropolis-BoldItalic.eot");src:url("/fonts/Metropolis-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-BoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-BoldItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Light";src:url("/fonts/Metropolis-Light.eot");src:url("/fonts/Metropolis-Light.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Light.woff2") format("woff2"),url("/fonts/Metropolis-Light.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-LightItalic";src:url("/fonts/Metropolis-LightItalic.eot");src:url("/fonts/Metropolis-LightItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-LightItalic.woff2") format("woff2"),url("/fonts/Metropolis-LightItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Regular";src:url("/fonts/Metropolis-Regular.eot");src:url("/fonts/Metropolis-Regular.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Regular.woff2") format("woff2"),url("/fonts/Metropolis-Regular.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-RegularItalic";src:url("/fonts/Metropolis-RegularItalic.eot");src:url("/fonts/Metropolis-RegularItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-RegularItalic.woff2") format("woff2"),url("/fonts/Metropolis-RegularItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Medium";src:url("/fonts/Metropolis-Medium.eot");src:url("/fonts/Metropolis-Medium.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Medium.woff2") format("woff2"),url("/fonts/Metropolis-Medium.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-MediumItalic";src:url("/fonts/Metropolis-MediumItalic.eot");src:url("/fonts/Metropolis-MediumItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-MediumItalic.woff2") format("woff2"),url("/fonts/Metropolis-MediumItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBold";src:url("/fonts/Metropolis-SemiBold.eot");src:url("/fonts/Metropolis-SemiBold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBold.woff2") format("woff2"),url("/fonts/Metropolis-SemiBold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBoldItalic";src:url("/fonts/Metropolis-SemiBoldItalic.eot");src:url("/fonts/Metropolis-SemiBoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-SemiBoldItalic.woff") format("woff");font-weight:normal;font-style:normal}footer .left-links{padding:0px;float:left}footer .left-links li img{vertical-align:bottom;margin-right:10px}footer .left-links li a{color:#333;font-weight:300;font-size:12px;font-family:"Metropolis-Light",Helvetica,sans-serif}footer .right-links{float:right}footer .right-links p{margin:0px}footer .right-links .copywrite{font-size:12px;padding-right:10px}footer .right-links .copywrite a{font-size:12px;color:#333;font-family:"Metropolis-Light",Helvetica,sans-serif}footer .right-links a{vertical-align:middle}@media only screen and (max-width: 767px){footer .left-links{width:100%;float:none}footer .left-links li{display:block;width:33%;float:left;padding-right:0px}footer .right-links{width:100%;padding-top:20px}footer .right-links .image{display:none}}body{font-family:"Metropolis-Light",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:36px}h2{font-size:28px}h3{font-size:22px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:"Metropolis-Medium",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}@font-face{font-family:"Metropolis-Bold";src:url("/fonts/Metropolis-Bold.eot");src:url("/fonts/Metropolis-Bold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Bold.woff2") format("woff2"),url("/fonts/Metropolis-Bold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-BoldItalic";src:url("/fonts/Metropolis-BoldItalic.eot");src:url("/fonts/Metropolis-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-BoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-BoldItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Light";src:url("/fonts/Metropolis-Light.eot");src:url("/fonts/Metropolis-Light.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Light.woff2") format("woff2"),url("/fonts/Metropolis-Light.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-LightItalic";src:url("/fonts/Metropolis-LightItalic.eot");src:url("/fonts/Metropolis-LightItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-LightItalic.woff2") format("woff2"),url("/fonts/Metropolis-LightItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Regular";src:url("/fonts/Metropolis-Regular.eot");src:url("/fonts/Metropolis-Regular.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Regular.woff2") format("woff2"),url("/fonts/Metropolis-Regular.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-RegularItalic";src:url("/fonts/Metropolis-RegularItalic.eot");src:url("/fonts/Metropolis-RegularItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-RegularItalic.woff2") format("woff2"),url("/fonts/Metropolis-RegularItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Medium";src:url("/fonts/Metropolis-Medium.eot");src:url("/fonts/Metropolis-Medium.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Medium.woff2") format("woff2"),url("/fonts/Metropolis-Medium.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-MediumItalic";src:url("/fonts/Metropolis-MediumItalic.eot");src:url("/fonts/Metropolis-MediumItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-MediumItalic.woff2") format("woff2"),url("/fonts/Metropolis-MediumItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBold";src:url("/fonts/Metropolis-SemiBold.eot");src:url("/fonts/Metropolis-SemiBold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBold.woff2") format("woff2"),url("/fonts/Metropolis-SemiBold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBoldItalic";src:url("/fonts/Metropolis-SemiBoldItalic.eot");src:url("/fonts/Metropolis-SemiBoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-SemiBoldItalic.woff") format("woff");font-weight:normal;font-style:normal}.hero{background-color:#0091DA;color:#fff}.hero .text-block{max-width:550px;padding:0px 0px 10px 0px}.hero .text-block p{margin-bottom:20px;font-size:18px;color:#fff}.hero h2{font-size:36px}.hero.homepage{background-image:url(/img/hero-image.png);background-position:center center;background-repeat:no-repeat;background-size:cover;padding-bottom:80px}@media only screen and (max-width: 767px){.hero .text-block{max-width:unset;margin-right:0px}.hero .button{display:block;text-align:center}.hero.homepage{background-image:none}}.grid-container{margin-top:-80px}.grid-container .grid.three{padding-bottom:20px}.grid-container .grid.three .card{position:relative;padding:30px 20px;background-color:#fff;text-align:center;box-shadow:0px 2px 10px rgba(0,0,0,0.2)}.grid-container .grid.three .card h3{color:#333;font-size:22px}.grid-container .grid.three .card p{color:#333}.introduction .grid.two{column-gap:140px;padding:35px 20px}.introduction .grid.two p{margin:0px;font-size:16px}.introduction .grid.two p.strong{color:#333}@media only screen and (max-width: 767px){.introduction{padding:0px 20px}.introduction .col:first-of-type{padding-bottom:50px}}.use-cases .grid{grid-template-columns:220px 1fr;margin-bottom:30px;grid-template-areas:"image text"}.use-cases .grid .image{background-color:#0091DA;text-align:center;display:flex;align-items:center;justify-content:center;grid-area:image}.use-cases .grid .image img{justify-self:center}.use-cases .grid .text{border:1px solid #F2F2F2;padding:30px;grid-area:text}.use-cases .grid .text a.button{display:block;max-width:138px;text-align:center;padding:5px 10px;min-width:unset}.use-cases .grid.image-right{grid-template-columns:1fr 220px;grid-template-areas:"text image"}@media only screen and (max-width: 767px){.use-cases .grid.image-right{grid-template-columns:1fr;grid-template-areas:"image" "text"}}@media only screen and (max-width: 767px){.use-cases .grid{grid-template-columns:1fr;grid-template-rows:minmax(160px, 1fr);grid-template-areas:"image" "text"}}.use-cases h2{color:#111}.use-cases p.strong{color:#1B3951;font-size:16px}.team{background-color:#1D428A}.team h2,.team h3,.team p{color:#fff}.team p{font-size:16px}.team a{color:#fff;font-weight:300;text-decoration:underline}.team .grid.three{row-gap:40px;margin:40px 0px}.team .bio{display:grid;grid-template-columns:120px 1fr;column-gap:20px}.team .bio .info{align-self:center}.team .bio .info p{margin:0px}.team .bio .info p.name{font-size:16px;font-family:"Metropolis-Medium",Helvetica,sans-serif}.team .bio .info p.position{font-size:14px}.hero.subpage{background-image:url(/img/blog-hero-image.png);background-position:center center;background-repeat:no-repeat;background-size:cover;padding-bottom:140px}.experimental .grid.three .col{padding:0px}.experimental .icon{background-color:#0091DA;padding:25px;min-height:95px;display:flex;align-items:center;justify-content:center}.experimental .content{padding:25px}.experimental .content .example{background-color:#F2F2F2}.blog{padding-bottom:50px}.blog .col{border:1px solid #F2F2F2}.blog .col img{width:100%}.blog .col .content{padding:0px 20px}.blog.landing{background-color:#fff;margin-top:-90px}.blog.landing h3 a{font-size:16px}.blog.landing .pagination{margin:30px auto 50px auto}.blog.landing .pagination ul{padding:0px;text-align:center}.blog.landing .pagination ul li{padding:0px}.blog.landing .pagination ul li a{padding:5px 10px}.blog.landing .pagination ul li a.active{background-color:#F2F2F2;border-radius:50%}.blog.landing .pagination ul li.left-arrow{margin-right:15px}.blog.landing .pagination ul li.right-arrow{margin-left:15px}.blog .blog-post{background-color:#fff;margin:-110px 0px 0px -30px;padding:30px 90px 30px 30px}.blog .blog-post .author{color:#0095D3;margin:0px}.blog .blog-post .date{color:#111;margin:0px;font-weight:600}.blog .blog-post .header,.blog .blog-post h4{color:#111;font-weight:600}.blog .blog-post a{font-size:16px}.blog .blog-post ul{list-style-type:disc;padding-left:20px}.blog .blog-post ul li:first-child{margin-top:10px}.blog .blog-post ul li{list-style-type:unset;display:list-item;margin-bottom:10px;font-size:14px;color:#333;line-height:1.6em;list-style-image:url(/img/arrow.svg)}.blog .blog-post ol li:first-child{margin-top:10px}.blog .blog-post ol li{list-style-type:decimal;display:list-item;margin-bottom:10px;font-size:16px;color:#333}.blog .blog-post code{background-color:#fff;color:#333;border:2px solid #EFEFEF;padding:2px 8px}.blog .blog-post code .c1{color:#0095D3;font-style:italic}.blog .blog-post code .se{color:#ff0000}.blog .blog-post pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.blog .blog-post pre code{display:block;border:15px solid #EFEFEF;padding:15px;margin-bottom:30px}.blog .blog-post img{max-width:100%}.blog .blog-post strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.getting-started{background-color:#F2F2F2;color:#111}.getting-started p{color:#111;font-size:16px}.getting-started .left-side{width:50%;float:left}.getting-started .right-side{width:25%;float:right}.getting-started h2{font-size:30px;margin-bottom:0px}.getting-started a{display:block;max-width:138px;text-align:center;padding:10px;min-width:unset}.getting-started .button{margin-top:50px;border:1px solid #0095D3}@media only screen and (max-width: 767px){.getting-started .wrapper{padding-bottom:40px}.getting-started .left-side{width:100%;float:none}.getting-started .right-side{width:100%;float:none}.getting-started .button{display:block;text-align:center;max-width:unset;margin-top:20px}}.community{background-color:#fff;margin-top:-90px;padding:30px 30px 50px 30px}.community .grid .col{border:1px solid #F2F2F2}.community .grid .col .icon{display:flex;align-items:center;justify-content:center;min-height:140px}.community .grid .col .content{padding:0px 20px 20px 20px;text-align:center}.community .grid .col .content h3{margin-top:0px}.docs{background-color:#fff;margin-top:-90px;padding:30px 30px 50px 30px}.docs .side-nav{width:25%;float:left}.docs .side-nav ul{padding-left:0px;margin-bottom:35px}.docs .side-nav ul li{display:list-item;margin-bottom:15px}.docs .side-nav ul li a{color:#333;font-size:14px}.docs .side-nav ul li a.active{color:#0095D3}.docs .side-nav ul li.heading{color:#111;font-size:14px}.docs .docs-content{width:75%;float:right}.docs .docs-content a{font-size:14px}.docs .docs-content ul{list-style-type:disc;padding-left:20px}.docs .docs-content ul li:first-child{margin-top:10px}.docs .docs-content ul li{list-style-type:unset;display:list-item;margin-bottom:10px;font-size:16px;color:#333;line-height:1.6em;list-style-image:url(/img/arrow.svg)}.docs .docs-content ol li:first-child{margin-top:10px}.docs .docs-content ol li{list-style-type:decimal;display:list-item;margin-bottom:10px;font-size:16px;color:#333}.docs .docs-content code{background-color:#fff;color:#333;border:2px solid #EFEFEF;padding:2px 8px}.docs .docs-content code .c1{color:#0095D3;font-style:italic}.docs .docs-content code .se{color:#ff0000}.docs .docs-content pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.docs .docs-content pre code{display:block;border:15px solid #EFEFEF;padding:15px;margin-bottom:30px}.docs .docs-content img{max-width:100%}.chroma{color:#272822;background-color:#fafafa}.chroma .err{color:#960050;background-color:#1e0010}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block}.chroma .hl{display:block;width:100%;background-color:#ffffcc}.chroma .lnt{margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f}.chroma .ln{margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f}.chroma .k{color:#00a8c8}.chroma .kc{color:#00a8c8}.chroma .kd{color:#00a8c8}.chroma .kn{color:#f92672}.chroma .kp{color:#00a8c8}.chroma .kr{color:#00a8c8}.chroma .kt{color:#00a8c8}.chroma .n{color:#111111}.chroma .na{color:#75af00}.chroma .nb{color:#111111}.chroma .bp{color:#111111}.chroma .nc{color:#75af00}.chroma .no{color:#00a8c8}.chroma .nd{color:#75af00}.chroma .ni{color:#111111}.chroma .ne{color:#75af00}.chroma .nf{color:#75af00}.chroma .fm{color:#111111}.chroma .nl{color:#111111}.chroma .nn{color:#111111}.chroma .nx{color:#75af00}.chroma .py{color:#111111}.chroma .nt{color:#f92672}.chroma .nv{color:#111111}.chroma .vc{color:#111111}.chroma .vg{color:#111111}.chroma .vi{color:#111111}.chroma .vm{color:#111111}.chroma .l{color:#ae81ff}.chroma .ld{color:#d88200}.chroma .s{color:#d88200}.chroma .sa{color:#d88200}.chroma .sb{color:#d88200}.chroma .sc{color:#d88200}.chroma .dl{color:#d88200}.chroma .sd{color:#d88200}.chroma .s2{color:#d88200}.chroma .se{color:#8045ff}.chroma .sh{color:#d88200}.chroma .si{color:#d88200}.chroma .sx{color:#d88200}.chroma .sr{color:#d88200}.chroma .s1{color:#d88200}.chroma .ss{color:#d88200}.chroma .m{color:#ae81ff}.chroma .mb{color:#ae81ff}.chroma .mf{color:#ae81ff}.chroma .mh{color:#ae81ff}.chroma .mi{color:#ae81ff}.chroma .il{color:#ae81ff}.chroma .mo{color:#ae81ff}.chroma .o{color:#f92672}.chroma .ow{color:#f92672}.chroma .p{color:#111111}.chroma .c{color:#75715e}.chroma .ch{color:#75715e}.chroma .cm{color:#75715e}.chroma .c1{color:#75715e}.chroma .cs{color:#75715e}.chroma .cp{color:#75715e}.chroma .cpf{color:#75715e}.chroma .ge{font-style:italic}.chroma .gs{font-weight:bold}
+body{font-family:"Metropolis-Light",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:36px}h2{font-size:28px}h3{font-size:22px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:"Metropolis-Medium",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.button img.button-icon{margin-left:5px;margin-right:5px;margin-bottom:-8px;width:24px;height:24px}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}@font-face{font-family:"Metropolis-Bold";src:url("/fonts/Metropolis-Bold.eot");src:url("/fonts/Metropolis-Bold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Bold.woff2") format("woff2"),url("/fonts/Metropolis-Bold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-BoldItalic";src:url("/fonts/Metropolis-BoldItalic.eot");src:url("/fonts/Metropolis-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-BoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-BoldItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Light";src:url("/fonts/Metropolis-Light.eot");src:url("/fonts/Metropolis-Light.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Light.woff2") format("woff2"),url("/fonts/Metropolis-Light.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-LightItalic";src:url("/fonts/Metropolis-LightItalic.eot");src:url("/fonts/Metropolis-LightItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-LightItalic.woff2") format("woff2"),url("/fonts/Metropolis-LightItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Regular";src:url("/fonts/Metropolis-Regular.eot");src:url("/fonts/Metropolis-Regular.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Regular.woff2") format("woff2"),url("/fonts/Metropolis-Regular.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-RegularItalic";src:url("/fonts/Metropolis-RegularItalic.eot");src:url("/fonts/Metropolis-RegularItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-RegularItalic.woff2") format("woff2"),url("/fonts/Metropolis-RegularItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Medium";src:url("/fonts/Metropolis-Medium.eot");src:url("/fonts/Metropolis-Medium.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Medium.woff2") format("woff2"),url("/fonts/Metropolis-Medium.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-MediumItalic";src:url("/fonts/Metropolis-MediumItalic.eot");src:url("/fonts/Metropolis-MediumItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-MediumItalic.woff2") format("woff2"),url("/fonts/Metropolis-MediumItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBold";src:url("/fonts/Metropolis-SemiBold.eot");src:url("/fonts/Metropolis-SemiBold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBold.woff2") format("woff2"),url("/fonts/Metropolis-SemiBold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBoldItalic";src:url("/fonts/Metropolis-SemiBoldItalic.eot");src:url("/fonts/Metropolis-SemiBoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-SemiBoldItalic.woff") format("woff");font-weight:normal;font-style:normal}header .wrapper{padding:10px 20px}header .desktop-links{float:right;margin:15px 0px 0px 0px;padding-left:0px}header a{color:#333;font-family:"Metropolis-Light",Helvetica,sans-serif}header a.active{font-family:"Metropolis-Medium",Helvetica,sans-serif}header li img{vertical-align:bottom;margin-right:10px}header .mobile{display:none}@media only screen and (min-width: 768px) and (max-width: 1279px){header .desktop-links li{padding-right:10px}}@media only screen and (max-width: 767px){header{position:relative}header .expanded-icon{display:none;padding:11px 3px 0px 0px}header .collapsed-icon{padding-top:12px}header .mobile-menu-visible .mobile{display:block}header .mobile-menu-visible .mobile .collapsed-icon{display:none}header .mobile-menu-visible .mobile .expanded-icon{display:block}header .desktop-links{display:none}header .mobile{display:block}header button{float:right}header button:focus{outline:none}header ul{padding-left:0px}header ul li{display:block;margin:20px 0px}header .mobile-menu{position:absolute;background-color:#fff;width:100%;top:70px;left:0px;padding-bottom:20px;display:none}header .mobile-menu .header-links{margin:0px 20px}header .mobile-menu .social{margin:0px 20px;padding-top:20px}header .mobile-menu .social img{vertical-align:middle;padding-right:10px}header .mobile-menu .social a{font-size:14px;padding-right:35px}header .mobile-menu .social a:last-of-type{padding-right:0px}}body{font-family:"Metropolis-Light",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:36px}h2{font-size:28px}h3{font-size:22px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:"Metropolis-Medium",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.button img.button-icon{margin-left:5px;margin-right:5px;margin-bottom:-8px;width:24px;height:24px}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}@font-face{font-family:"Metropolis-Bold";src:url("/fonts/Metropolis-Bold.eot");src:url("/fonts/Metropolis-Bold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Bold.woff2") format("woff2"),url("/fonts/Metropolis-Bold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-BoldItalic";src:url("/fonts/Metropolis-BoldItalic.eot");src:url("/fonts/Metropolis-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-BoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-BoldItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Light";src:url("/fonts/Metropolis-Light.eot");src:url("/fonts/Metropolis-Light.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Light.woff2") format("woff2"),url("/fonts/Metropolis-Light.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-LightItalic";src:url("/fonts/Metropolis-LightItalic.eot");src:url("/fonts/Metropolis-LightItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-LightItalic.woff2") format("woff2"),url("/fonts/Metropolis-LightItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Regular";src:url("/fonts/Metropolis-Regular.eot");src:url("/fonts/Metropolis-Regular.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Regular.woff2") format("woff2"),url("/fonts/Metropolis-Regular.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-RegularItalic";src:url("/fonts/Metropolis-RegularItalic.eot");src:url("/fonts/Metropolis-RegularItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-RegularItalic.woff2") format("woff2"),url("/fonts/Metropolis-RegularItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Medium";src:url("/fonts/Metropolis-Medium.eot");src:url("/fonts/Metropolis-Medium.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Medium.woff2") format("woff2"),url("/fonts/Metropolis-Medium.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-MediumItalic";src:url("/fonts/Metropolis-MediumItalic.eot");src:url("/fonts/Metropolis-MediumItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-MediumItalic.woff2") format("woff2"),url("/fonts/Metropolis-MediumItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBold";src:url("/fonts/Metropolis-SemiBold.eot");src:url("/fonts/Metropolis-SemiBold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBold.woff2") format("woff2"),url("/fonts/Metropolis-SemiBold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBoldItalic";src:url("/fonts/Metropolis-SemiBoldItalic.eot");src:url("/fonts/Metropolis-SemiBoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-SemiBoldItalic.woff") format("woff");font-weight:normal;font-style:normal}footer .left-links{padding:0px;float:left}footer .left-links li img{vertical-align:bottom;margin-right:10px}footer .left-links li a{color:#333;font-weight:300;font-size:12px;font-family:"Metropolis-Light",Helvetica,sans-serif}footer .right-links{float:right}footer .right-links p{margin:0px}footer .right-links .copywrite{font-size:12px;padding-right:10px}footer .right-links .copywrite a{font-size:12px;color:#333;font-family:"Metropolis-Light",Helvetica,sans-serif}footer .right-links a{vertical-align:middle}@media only screen and (max-width: 767px){footer .left-links{width:100%;float:none}footer .left-links li{display:block;width:33%;float:left;padding-right:0px}footer .right-links{width:100%;padding-top:20px}footer .right-links .image{display:none}}body{font-family:"Metropolis-Light",Helvetica,sans-serif;margin:0px;line-height:1.25}.wrapper{max-width:980px;margin:0px auto;padding:20px}@media only screen and (max-width: 767px){.wrapper{max-width:100%}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{font-weight:300}h1{font-size:36px}h2{font-size:28px}h3{font-size:22px}h4{font-size:18px}li{list-style-type:none;display:inline;padding-right:25px;font-size:14px;line-height:1.7em}li:last-of-type{padding-right:0px}p{line-height:1.7em;font-weight:300;font-size:16px;color:#333}a{font-size:16px;text-decoration:none;color:#0095D3;font-family:"Metropolis-Medium",Helvetica,sans-serif}button{background-color:unset;border:none}.button{color:#0095D3;font-size:12px;font-weight:600;background-color:#fff;border-radius:3px;padding:14px 10px;min-width:200px;text-transform:uppercase;border:1px solid #fff}.button.secondary{background-color:#0091DA;color:#fff}.button.tertiary{border:1px solid #0095D3}.button img.button-icon{margin-left:5px;margin-right:5px;margin-bottom:-8px;width:24px;height:24px}.buttons{margin-top:40px}.buttons .button:first-of-type{margin-right:30px}@media only screen and (max-width: 767px){.buttons .button:first-of-type{margin:0px 0px 20px 0px}}.strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.bg-grey{background-color:#F2F2F2}.grid.three{display:grid;grid-template-columns:1fr 1fr 1fr;row-gap:20px;column-gap:20px}@media only screen and (max-width: 767px){.grid.three{grid-template-columns:1fr}}.grid.two{display:grid;grid-template-columns:1fr 1fr}@media only screen and (max-width: 767px){.grid.two{grid-template-columns:1fr}}@font-face{font-family:"Metropolis-Bold";src:url("/fonts/Metropolis-Bold.eot");src:url("/fonts/Metropolis-Bold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Bold.woff2") format("woff2"),url("/fonts/Metropolis-Bold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-BoldItalic";src:url("/fonts/Metropolis-BoldItalic.eot");src:url("/fonts/Metropolis-BoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-BoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-BoldItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Light";src:url("/fonts/Metropolis-Light.eot");src:url("/fonts/Metropolis-Light.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Light.woff2") format("woff2"),url("/fonts/Metropolis-Light.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-LightItalic";src:url("/fonts/Metropolis-LightItalic.eot");src:url("/fonts/Metropolis-LightItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-LightItalic.woff2") format("woff2"),url("/fonts/Metropolis-LightItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Regular";src:url("/fonts/Metropolis-Regular.eot");src:url("/fonts/Metropolis-Regular.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Regular.woff2") format("woff2"),url("/fonts/Metropolis-Regular.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-RegularItalic";src:url("/fonts/Metropolis-RegularItalic.eot");src:url("/fonts/Metropolis-RegularItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-RegularItalic.woff2") format("woff2"),url("/fonts/Metropolis-RegularItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-Medium";src:url("/fonts/Metropolis-Medium.eot");src:url("/fonts/Metropolis-Medium.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-Medium.woff2") format("woff2"),url("/fonts/Metropolis-Medium.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-MediumItalic";src:url("/fonts/Metropolis-MediumItalic.eot");src:url("/fonts/Metropolis-MediumItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-MediumItalic.woff2") format("woff2"),url("/fonts/Metropolis-MediumItalic.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBold";src:url("/fonts/Metropolis-SemiBold.eot");src:url("/fonts/Metropolis-SemiBold.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBold.woff2") format("woff2"),url("/fonts/Metropolis-SemiBold.woff") format("woff");font-weight:normal;font-style:normal}@font-face{font-family:"Metropolis-SemiBoldItalic";src:url("/fonts/Metropolis-SemiBoldItalic.eot");src:url("/fonts/Metropolis-SemiBoldItalic.eot?#iefix") format("embedded-opentype"),url("/fonts/Metropolis-SemiBoldItalic.woff2") format("woff2"),url("/fonts/Metropolis-SemiBoldItalic.woff") format("woff");font-weight:normal;font-style:normal}.hero{background-color:#0091DA;color:#fff}.hero .text-block{max-width:550px;padding:0px 0px 10px 0px}.hero .text-block p{margin-bottom:20px;font-size:18px;color:#fff}.hero h2{font-size:36px}.hero.homepage{background-image:url(/img/hero-image.png);background-position:center center;background-repeat:no-repeat;background-size:cover;padding-bottom:80px}@media only screen and (max-width: 767px){.hero .text-block{max-width:unset;margin-right:0px}.hero .button{display:block;text-align:center}.hero.homepage{background-image:none}}.grid-container{margin-top:-80px}.grid-container .grid.three{padding-bottom:20px}.grid-container .grid.three .card{position:relative;padding:30px 20px;background-color:#fff;text-align:center;box-shadow:0px 2px 10px rgba(0,0,0,0.2)}.grid-container .grid.three .card h3{color:#333;font-size:22px}.grid-container .grid.three .card p{color:#333}.introduction .grid.two{column-gap:140px;padding:35px 20px}.introduction .grid.two p{margin:0px;font-size:16px}.introduction .grid.two p.strong{color:#333}@media only screen and (max-width: 767px){.introduction{padding:0px 20px}.introduction .col:first-of-type{padding-bottom:50px}}.use-cases .grid{grid-template-columns:220px 1fr;margin-bottom:30px;grid-template-areas:"image text"}.use-cases .grid .image{background-color:#0091DA;text-align:center;display:flex;align-items:center;justify-content:center;grid-area:image}.use-cases .grid .image img{justify-self:center}.use-cases .grid .text{border:1px solid #F2F2F2;padding:30px;grid-area:text}.use-cases .grid .text a.button{display:block;max-width:138px;text-align:center;padding:5px 10px;min-width:unset}.use-cases .grid.image-right{grid-template-columns:1fr 220px;grid-template-areas:"text image"}@media only screen and (max-width: 767px){.use-cases .grid.image-right{grid-template-columns:1fr;grid-template-areas:"image" "text"}}@media only screen and (max-width: 767px){.use-cases .grid{grid-template-columns:1fr;grid-template-rows:minmax(160px, 1fr);grid-template-areas:"image" "text"}}.use-cases h2{color:#111}.use-cases p.strong{color:#1B3951;font-size:16px}.team{background-color:#1D428A}.team h2,.team h3,.team p{color:#fff}.team p{font-size:16px}.team a{color:#fff;font-weight:300;text-decoration:underline}.team .grid.three{row-gap:40px;margin:40px 0px}.team .bio{display:grid;grid-template-columns:120px 1fr;column-gap:20px}.team .bio .info{align-self:center}.team .bio .info p{margin:0px}.team .bio .info p.name{font-size:16px;font-family:"Metropolis-Medium",Helvetica,sans-serif}.team .bio .info p.position{font-size:14px}.hero.subpage{background-image:url(/img/blog-hero-image.png);background-position:center center;background-repeat:no-repeat;background-size:cover;padding-bottom:140px}.experimental .grid.three .col{padding:0px}.experimental .icon{background-color:#0091DA;padding:25px;min-height:95px;display:flex;align-items:center;justify-content:center}.experimental .content{padding:25px}.experimental .content .example{background-color:#F2F2F2}.blog{padding-bottom:50px}.blog .col{border:1px solid #F2F2F2}.blog .col img{width:100%}.blog .col .content{padding:0px 20px}.blog.landing{background-color:#fff;margin-top:-90px}.blog.landing h3 a{font-size:16px}.blog.landing .pagination{margin:30px auto 50px auto}.blog.landing .pagination ul{padding:0px;text-align:center}.blog.landing .pagination ul li{padding:0px}.blog.landing .pagination ul li a{padding:5px 10px}.blog.landing .pagination ul li a.active{background-color:#F2F2F2;border-radius:50%}.blog.landing .pagination ul li.left-arrow{margin-right:15px}.blog.landing .pagination ul li.right-arrow{margin-left:15px}.blog .blog-post{background-color:#fff;margin:-110px 0px 0px -30px;padding:30px 90px 30px 30px}.blog .blog-post .author{color:#0095D3;margin:0px}.blog .blog-post .date{color:#111;margin:0px;font-weight:600}.blog .blog-post .header,.blog .blog-post h4{color:#111;font-weight:600}.blog .blog-post a{font-size:16px}.blog .blog-post ul{list-style-type:disc;padding-left:20px}.blog .blog-post ul li:first-child{margin-top:10px}.blog .blog-post ul li{list-style-type:unset;display:list-item;margin-bottom:10px;font-size:16px;color:#333;list-style-image:url(/img/arrow.svg)}.blog .blog-post ol li:first-child{margin-top:10px}.blog .blog-post ol li{list-style-type:decimal;display:list-item;margin-bottom:10px;font-size:16px;color:#333}.blog .blog-post code{background-color:#fff;color:#333;border:2px solid #EFEFEF;padding:2px 8px}.blog .blog-post code .c1{color:#0095D3;font-style:italic}.blog .blog-post code .se{color:#ff0000}.blog .blog-post pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.blog .blog-post pre code{display:block;border:15px solid #EFEFEF;padding:15px;margin-bottom:30px}.blog .blog-post img{max-width:100%}.blog .blog-post strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.getting-started{background-color:#F2F2F2;color:#111}.getting-started p{color:#111;font-size:16px}.getting-started .left-side{width:50%;float:left}.getting-started .right-side{width:25%;float:right}.getting-started h2{font-size:30px;margin-bottom:0px}.getting-started a{display:block;max-width:138px;text-align:center;padding:10px;min-width:unset}.getting-started .button{margin-top:50px;border:1px solid #0095D3}@media only screen and (max-width: 767px){.getting-started .wrapper{padding-bottom:40px}.getting-started .left-side{width:100%;float:none}.getting-started .right-side{width:100%;float:none}.getting-started .button{display:block;text-align:center;max-width:unset;margin-top:20px}}.community{background-color:#fff;margin-top:-90px;padding:30px 30px 50px 30px}.community .grid .col{border:1px solid #F2F2F2}.community .grid .col .icon{display:flex;align-items:center;justify-content:center;min-height:140px}.community .grid .col .content{padding:0px 20px 20px 20px;text-align:center}.community .grid .col .content h3{margin-top:0px}.docs{background-color:#fff;margin-top:-90px;padding:30px 30px 50px 30px}.docs .side-nav{width:25%;float:left}@media only screen and (max-width: 1279px){.docs .side-nav{width:100%;float:none}}.docs .side-nav ul{padding-left:0px;margin-bottom:35px}.docs .side-nav ul ul{padding-left:15px;margin-top:10px;margin-bottom:15px}.docs .side-nav ul li{display:list-item;margin-bottom:15px}.docs .side-nav ul li a{color:#333;font-size:14px}.docs .side-nav ul li a.active{color:#0095D3}.docs .side-nav ul li.heading{color:#111;font-size:14px}.docs .docs-content{width:75%;float:right}@media only screen and (max-width: 1279px){.docs .docs-content{width:100%;float:none}}.docs .docs-content a{font-size:16px}.docs .docs-content ul{list-style-type:disc;padding-left:20px}.docs .docs-content ul li:first-child{margin-top:10px}.docs .docs-content ul li{list-style-type:unset;display:list-item;margin-bottom:10px;font-size:16px;color:#333;line-height:1.6em;list-style-image:url(/img/arrow.svg)}.docs .docs-content ol li:first-child{margin-top:10px}.docs .docs-content ol li{list-style-type:decimal;display:list-item;margin-bottom:10px;font-size:16px;color:#333}.docs .docs-content code{background-color:#fff;color:#333;border:2px solid #EFEFEF;padding:2px 8px}.docs .docs-content code .c1{color:#0095D3;font-style:italic}.docs .docs-content code .se{color:#ff0000}.docs .docs-content pre{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}.docs .docs-content pre code{display:block;border:15px solid #EFEFEF;padding:15px;margin-bottom:30px;font-size:14px}.docs .docs-content img{max-width:100%}.docs .docs-content strong{font-family:"Metropolis-Medium",Helvetica,sans-serif}.docs .danger{padding:10px;font-family:"Metropolis-LightItalic",Helvetica,sans-serif}.docs .danger .danger-icon{float:left;padding:40px;width:24px;height:24px}.docs .button{white-space:nowrap}.docs .button a{font-size:14px}.docs table td{padding:10px 30px}.chroma{color:#272822;background-color:#fafafa}.chroma .err{color:#960050;background-color:#1e0010}.chroma .lntd{vertical-align:top;padding:0;margin:0;border:0}.chroma .lntable{border-spacing:0;padding:0;margin:0;border:0;width:auto;overflow:auto;display:block}.chroma .hl{display:block;width:100%;background-color:#ffffcc}.chroma .lnt{margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f}.chroma .ln{margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f}.chroma .k{color:#00a8c8}.chroma .kc{color:#00a8c8}.chroma .kd{color:#00a8c8}.chroma .kn{color:#f92672}.chroma .kp{color:#00a8c8}.chroma .kr{color:#00a8c8}.chroma .kt{color:#00a8c8}.chroma .n{color:#111111}.chroma .na{color:#75af00}.chroma .nb{color:#111111}.chroma .bp{color:#111111}.chroma .nc{color:#75af00}.chroma .no{color:#00a8c8}.chroma .nd{color:#75af00}.chroma .ni{color:#111111}.chroma .ne{color:#75af00}.chroma .nf{color:#75af00}.chroma .fm{color:#111111}.chroma .nl{color:#111111}.chroma .nn{color:#111111}.chroma .nx{color:#75af00}.chroma .py{color:#111111}.chroma .nt{color:#f92672}.chroma .nv{color:#111111}.chroma .vc{color:#111111}.chroma .vg{color:#111111}.chroma .vi{color:#111111}.chroma .vm{color:#111111}.chroma .l{color:#ae81ff}.chroma .ld{color:#d88200}.chroma .s{color:#d88200}.chroma .sa{color:#d88200}.chroma .sb{color:#d88200}.chroma .sc{color:#d88200}.chroma .dl{color:#d88200}.chroma .sd{color:#d88200}.chroma .s2{color:#d88200}.chroma .se{color:#8045ff}.chroma .sh{color:#d88200}.chroma .si{color:#d88200}.chroma .sx{color:#d88200}.chroma .sr{color:#d88200}.chroma .s1{color:#d88200}.chroma .ss{color:#d88200}.chroma .m{color:#ae81ff}.chroma .mb{color:#ae81ff}.chroma .mf{color:#ae81ff}.chroma .mh{color:#ae81ff}.chroma .mi{color:#ae81ff}.chroma .il{color:#ae81ff}.chroma .mo{color:#ae81ff}.chroma .o{color:#f92672}.chroma .ow{color:#f92672}.chroma .p{color:#111111}.chroma .c{color:#75715e}.chroma .ch{color:#75715e}.chroma .cm{color:#75715e}.chroma .c1{color:#75715e}.chroma .cs{color:#75715e}.chroma .cp{color:#75715e}.chroma .cpf{color:#75715e}.chroma .ge{font-style:italic}.chroma .gs{font-weight:bold}
/*# sourceMappingURL=style.css.map */
\ No newline at end of file
diff --git a/site/themes/pinniped/assets/scss/_base.scss b/site/themes/pinniped/assets/scss/_base.scss
index f10c3d71..2b329929 100644
--- a/site/themes/pinniped/assets/scss/_base.scss
+++ b/site/themes/pinniped/assets/scss/_base.scss
@@ -97,6 +97,13 @@ button {
&.tertiary {
border: 1px solid $blue;
}
+ img.button-icon {
+ margin-left: 5px;
+ margin-right: 5px;
+ margin-bottom: -8px;
+ width: 24px;
+ height: 24px;
+ }
}
.buttons {
margin-top: 40px;
diff --git a/site/themes/pinniped/assets/scss/_components.scss b/site/themes/pinniped/assets/scss/_components.scss
index dd301bec..f6a48cca 100644
--- a/site/themes/pinniped/assets/scss/_components.scss
+++ b/site/themes/pinniped/assets/scss/_components.scss
@@ -273,9 +273,8 @@
list-style-type: unset;
display: list-item;
margin-bottom: 10px;
- font-size: 14px;
+ font-size: 16px;
color: $darkgrey;
- line-height: 1.6em;
list-style-image: url(/img/arrow.svg);
}
}
@@ -408,9 +407,19 @@
.side-nav {
width: 25%;
float: left;
+ @include breakpoint(small-medium) {
+ width: 100%;
+ float: none;
+ }
+
ul {
padding-left: 0px;
margin-bottom: 35px;
+ ul {
+ padding-left: 15px;
+ margin-top: 10px;
+ margin-bottom: 15px;
+ }
li {
display: list-item;
margin-bottom: 15px;
@@ -431,8 +440,12 @@
.docs-content {
width: 75%;
float: right;
+ @include breakpoint(small-medium) {
+ width: 100%;
+ float: none;
+ }
a {
- font-size: 14px;
+ font-size: 16px;
}
ul {
list-style-type: disc;
@@ -486,10 +499,37 @@
border: 15px solid #EFEFEF;
padding: 15px;
margin-bottom: 30px;
+ font-size: 14px;
}
}
img {
max-width: 100%;
}
+ strong {
+ font-family: $metropolis-medium;
+ }
+ }
+ .danger {
+ .danger-icon {
+ float: left;
+ padding: 40px;
+ width: 24px;
+ height: 24px;
+ }
+ padding: 10px;
+ font-family: $metropolis-light-italic;
+ }
+
+ .button {
+ white-space: nowrap;
+ a {
+ font-size: 14px;
+ }
+ }
+
+ table {
+ td {
+ padding: 10px 30px;
+ }
}
}
\ No newline at end of file
diff --git a/site/themes/pinniped/layouts/_default/baseof.html b/site/themes/pinniped/layouts/_default/baseof.html
index b68a0f83..2977d22e 100644
--- a/site/themes/pinniped/layouts/_default/baseof.html
+++ b/site/themes/pinniped/layouts/_default/baseof.html
@@ -19,7 +19,6 @@
{{ partial "header" . }}
{{ block "main" . }}{{ end }}
- {{ partial "getting-started" . }}
{{ partial "footer" . }}