diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1eed0fc4cc..c5af89c3f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,9 +18,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: Azure/setup-helm@v1 - - run: | - helm lint helm/charts/engine/ - helm lint helm/charts/registry/ + - run: make helm/lint tag=0.0.0-development go-lint: runs-on: ubuntu-latest steps: diff --git a/Makefile b/Makefile index ad6db50f0a..660d47aff4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -tag := $(shell git describe --tags) +tag := $(patsubst v%,%,$(shell git describe --tags)) generate: go generate ./... @@ -14,15 +14,31 @@ test-all: go test ./... .PHONY: helm -helm: - helm package -d ./helm helm/charts/registry helm/charts/engine --version $(tag:v%=%) --app-version $(tag:v%=%) - helm repo index ./helm +helm: helm/engine.tgz helm/infra.tgz + helm repo index helm + +helm/%.tgz: helm/charts/% + helm package -d $(@D) $< --version $(tag) --app-version $(tag) + +helm/charts/infra/charts/: + mkdir -p $@ + +helm/charts/infra/charts/%.tgz: helm/%.tgz helm/charts/infra/charts/ + ln -sf $(realpath $<) $(@D) + +helm/infra.tgz: helm/charts/infra/charts/engine-$(tag).tgz + +helm/lint: helm + helm lint helm/charts/* + +helm/clean: + $(RM) -r helm/*.tgz helm/charts/infra/charts .PHONY: docs docs: go run ./internal/docgen -clean: +clean: helm/clean $(RM) -r dist .PHONY: openapi @@ -41,39 +57,46 @@ goreleaser: build: goreleaser goreleaser build --snapshot --rm-dist +export IMAGE_TAG=0.0.0-development + build/docker: - docker build --build-arg TELEMETRY_WRITE_KEY=${TELEMETRY_WRITE_KEY} --build-arg CRASH_REPORTING_DSN=${CRASH_REPORTING_DSN} . -t infrahq/infra:0.0.0-development + docker build --build-arg TELEMETRY_WRITE_KEY=${TELEMETRY_WRITE_KEY} --build-arg CRASH_REPORTING_DSN=${CRASH_REPORTING_DSN} . -t infrahq/infra:$(IMAGE_TAG) + +export OKTA_SECRET=infra-registry-okta + +%.yaml: %.yaml.in + envsubst <$< >$@ + +docker-desktop.yaml: docker-desktop.yaml.in + +NS=$(patsubst %,-n %,$(NAMESPACE)) +VALUES=docker-desktop.yaml -dev: +dev: $(VALUES) helm build/docker # docker desktop setup for the dev environment # create a token and get the token secret from: # https://dev-02708987-admin.okta.com/admin/access/api/tokens # get client secret from: # https://dev-02708987-admin.okta.com/admin/app/oidc_client/instance/0oapn0qwiQPiMIyR35d6/#tab-general # create the required secret with: - # kubectl create secret generic infra-registry-okta -n infrahq --from-literal=clientSecret=$$OKTA_CLIENT_SECRET --from-literal=apiToken=$$OKTA_API_TOKEN + # kubectl $(NS) create secret generic $(OKTA_SECRET) --from-literal=clientSecret=$$OKTA_CLIENT_SECRET --from-literal=apiToken=$$OKTA_API_TOKEN kubectl config use-context docker-desktop - make build/docker - helm upgrade --install infra-registry ./helm/charts/registry --namespace infrahq --create-namespace --set image.pullPolicy=Never --set image.tag=0.0.0-development --set-file config=./infra.yaml --set logLevel=debug - kubectl config set-context --current --namespace=infrahq - kubectl wait --for=condition=available --timeout=600s deployment/infra-registry --namespace infrahq - helm upgrade --install infra-engine ./helm/charts/engine --namespace infrahq --set image.pullPolicy=Never --set image.tag=0.0.0-development --set name=dd --set registry=infra-registry --set apiKey=$$(kubectl get secrets/infra-registry --template={{.data.engineApiKey}} --namespace infrahq | base64 -D) --set service.ports[0].port=8443 --set service.ports[0].name=https --set service.ports[0].targetPort=443 --set logLevel=debug - kubectl rollout restart deployment/infra-registry --namespace infrahq - kubectl rollout restart deployment/infra-engine --namespace infrahq - ROOT_TOKEN=$$(kubectl --namespace infrahq get secrets infra-registry -o jsonpath='{.data.rootApiKey}' | base64 -D); \ - echo Root token is $$ROOT_TOKEN + kubectl $(NS) get secrets $(INFRA_REGISTRY_OKTA) >/dev/null + helm $(NS) upgrade --install --create-namespace $(patsubst %,-f %,$(VALUES)) --wait infra helm/charts/infra + @[ -z "$(NS)" ] || kubectl config set-context --current --namespace=$(NAMESPACE) + @echo Root token is $$(kubectl $(NS) get secrets infra-registry -o jsonpath='{.data.root-key}' | base64 --decode) dev/clean: kubectl config use-context docker-desktop - helm uninstall --namespace infrahq infra-registry || true - helm uninstall --namespace infrahq infra-engine || true + helm $(NS) uninstall infra || true + helm $(NS) uninstall infra-engine || true release: goreleaser goreleaser release -f .goreleaser.yml --rm-dist release/docker: - docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg BUILDVERSION=$(tag:v%=%) --build-arg TELEMETRY_WRITE_KEY=${TELEMETRY_WRITE_KEY} --build-arg CRASH_REPORTING_DSN=${CRASH_REPORTING_DSN} . -t infrahq/infra:$(tag:v%=%) -t infrahq/infra + docker buildx build --push --platform linux/amd64,linux/arm64 --build-arg BUILDVERSION=$(tag) --build-arg TELEMETRY_WRITE_KEY=${TELEMETRY_WRITE_KEY} --build-arg CRASH_REPORTING_DSN=${CRASH_REPORTING_DSN} . -t infrahq/infra:$(tag) -t infrahq/infra release/helm: helm aws s3 --region us-east-2 sync helm s3://helm.infrahq.com --exclude "*" --include "index.yaml" --include "*.tgz" diff --git a/README.md b/README.md index afcc43f19a..8235ca014c 100644 --- a/README.md +++ b/README.md @@ -4,69 +4,76 @@ ## Introduction -Infra is **identity and access management** for Kubernetes. Provide any user fine-grained access to Kubernetes clusters via existing identity providers such as Okta, Google Accounts, Azure Active Directory and more. +Infra is **identity and access management** for your cloud infrastructure. It puts the power of fine-grained access to infrastructure like Kubernetes in your hands via existing identity providers such as Okta, Google Accounts, Azure Active Directory and more. **Features**: * Single-command access: `infra login` -* No more out-of-sync kubeconfig files +* No more out-of-sync user configurations * Fine-grained role assignment * Onboard and offboard users via Okta (Azure AD, Google, GitHub coming soon) * Audit logs for who did what, when (coming soon) ## Quickstart -### Install Infra Registry - **Prerequisites:** -* [Helm](https://helm.sh/) +* [Helm](https://helm.sh/) (v3+) +* [Kubernetes](https://kubernetes.io/) (v1.14+) + +### Install Infra ```bash -helm repo add infrahq https://helm.infrahq.com +helm repo add infrahq https://helm.infrahq.com/ helm repo update -helm install infra-registry infrahq/registry --namespace infrahq --create-namespace +helm install -n infrahq --create-namespace infra infrahq/infra ``` -### Connect Kubernetes cluster to Infra Registry +See [Helm Chart reference](./helm.md) for a complete list of options configurable through Helm. -Once the load balancer for the Infra Registry is available, run the following commands to retrieve Infra Registry information and its engine API key: +### Configure Infra -```bash -INFRA_REGISTRY=$(kubectl --namespace infrahq get services infra-registry -o jsonpath="{.status.loadBalancer.ingress[*]['ip', 'hostname']}") -ENGINE_API_KEY=$(kubectl --namespace infrahq get secrets infra-registry -o jsonpath='{.data.engineApiKey}' | base64 -d) -``` +This example configuration uses Okta and grants the "Everyone" group read-only access to the default namespace. You will need: -Then, install Infra Engine in the Kubernetes context of the cluster you want to connect to Infra Registry: - -```bash -helm install infra-engine infrahq/engine --namespace infrahq --set registry=$INFRA_REGISTRY --set apiKey=$ENGINE_API_KEY -``` +* Okta domain +* Okta client ID +* Okta client secret +* Okta API token +* Cluster name -### Connect an identity provider +See [Okta](./docs/sources/okta.md) for detailed Okta configuration steps. -First, add Okta via an `infra.yaml` configuration file: - -* [Okta configuration guide](./docs/okta.md) - -Next, add the following to your `infra.yaml` configuration file to grant everyone view access to the cluster: +Cluster name is auto-discovered or can be set statically in Helm with `engine.name`. ```yaml -groups: - - name: Everyone # example group - source: okta - roles: - - name: view - kind: cluster-role - destinations: - - name: +# example values.yaml +--- +config: + sources: + - kind: okta + domain: + clientId: + clientSecret: + apiToken: + groups: + - name: Everyone + roles: + - kind: role + name: viewer + destinations: + - name: + namespace: default ``` -Then, update your Infra Registry with this new config: +See the [Configuration reference](./docs/configuration.md) for a complete list of configurable options. -```bash -helm upgrade infra-registry infrahq/registry --namespace infrahq --set-file config=./infra.yaml +### Update Infra With Your Configuration + +``` +helm repo update +helm upgrade -n infrahq -f values.yaml infra infrahq/infra ``` ### Install Infra CLI +
Debian, Ubuntu @@ -103,46 +110,79 @@ helm upgrade infra-registry infrahq/registry --namespace infrahq --set-file conf ```
-### Access infrastructure +### Access Your Infrastructure -```bash -infra login -``` +First you need to get your Infra endpoint. This step may be different depending on your service type. -After login, Infra will automatically synchronize all the Kubernetes clusters configured for the user into their default kubeconfig file. +
+ Ingress -That's it! You now have access to your cluster via Okta. To list all the clusters, run `infra list`. + ``` + INFRA_HOST=$(kubectl -n infrahq get ingress -l infrahq.com/component=registry -o jsonpath="{.items[].status.loadBalancer.ingress[*]['ip', 'hostname']}") + ``` +
-## Upgrading Infra +
+ LoadBalancer -First, update the Helm repo: + Note: It may take a few minutes for the LoadBalancer endpoint to be assigned. You can watch the status of the service with: -```bash -helm repo update -``` + ``` + kubectl -n infrahq get services -l infrahq.com/component=registry -w + ``` -Then, update the Infra Registry + ``` + INFRA_HOST=$(kubectl -n infrahq get services -l infrahq.com/component=registry -o jsonpath="{.items[].status.loadBalancer.ingress[*]['ip', 'hostname']}") + ``` +
-```bash -helm upgrade infra-registry infrahq/registry --namespace infrahq -``` +
+ ClusterIP -Lastly, update any Infra Engines: + ``` + CONTAINER_PORT=$(kubectl -n infrahq get services -l infrahq.com/component=registry -o jsonpath="{.items[].spec.ports[0].port}") + kubectl -n infrahq port-forward service/infra-registry 8080:$CONTAINER_PORT & + INFRA_HOST='localhost:8080' + ``` +
+ +Once you have your infra host, it is time to login. ```bash -helm upgrade infra-engine infrahq/engine --namespace infrahq +infra login $INFRA_HOST ``` +Follow the instructions on screen to complete the login process. + +See the [Infra CLI reference](./docs/cli.md) for more ways to use `infra`. + ## Next Steps -* [Update roles](./docs/permissions.md) -* [Add a custom domain](./docs/domain.md) to make it easy for sharing with your team -* [Connect more Kubernetes clusters](./docs/connect.md) -## Documentation -* [Okta Reference](./docs/okta.md) +### Connect Additional Identity Sources + +* [Sources](./docs/sources) + * [Okta](./docs/sources/okta.md) + +### Connect Additional Infrastructure Destinations + +* [Destinations](./docs/destinations) + * [Kubernetes](./docs/destinations/kubernetes.md) + +### Upgrade Infra + +``` +helm repo update +helm upgrade -f values.yaml infra infrahq.com/infra +``` + +## [Security](./docs/security.md) + +We take security very seriously. If you have found a security vulnerability please disclose it privately to us by email via [security@infrahq.com](mailto:security@infrahq.com). + +## [Documentation](./docs) + +* [API Reference](./docs/api.md) +* [Infra CLI Reference](./docs/cli.md) * [Helm Chart Reference](./docs/helm.md) -* [CLI Reference](./docs/cli.md) * [Contributing](./docs/contributing.md) - -## Security -We take security very seriously. If you have found a security vulnerability please disclose it privately to us by email via [security@infrahq.com](mailto:security@infrahq.com) +* [License](./LICENSE) diff --git a/docker-desktop.yaml.in b/docker-desktop.yaml.in new file mode 100644 index 0000000000..33aa15dc66 --- /dev/null +++ b/docker-desktop.yaml.in @@ -0,0 +1,22 @@ +image: + tag: $IMAGE_TAG + pullPolicy: Never + + config: + sources: + - kind: okta + domain: $OKTA_DOMAIN + clientId: $OKTA_CLIENT_ID + clientSecret: $OKTA_SECRET/clientSecret + apiToken: $OKTA_SECRET/apiToken + +engine: + name: docker-desktop + image: + tag: $IMAGE_TAG + pullPolicy: Never + service: + ports: + - name: https + port: 8443 + targetPort: 443 diff --git a/docs/configuration.md b/docs/configuration.md index 15c6364655..df49d736ff 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,48 +1,101 @@ -# Configuring Infra - -* [Overview](#overview) -* [Create a configuration file](#create-a-configuration-file) -* [Full Example](#full-example) -* [Reference](#reference) - * [`sources`](#sources) - * [`groups`](#groups) - * [`users`](#users) - * [`roles`](#roles) - * [`destinations`](#destinations) +# Configuration ## Overview -For teams who require configuration to be stored in version control, Infra can be managed via a configuration file, `infra.yaml`. +For teams who require configuration to be stored in version control, Infra can be managed via Helm values or a standalone file. -## Create a configuration file +## Helm Values -First, create a config file `infra.yaml`: +Infra configuration can be added to Helm values under the `config` key. + +First, create a `values.yaml`. If a `values.yaml` already exists, update it to include the following: ``` -sources: - - kind: okta - domain: acme.okta.com - clientId: 0oapn0qwiQPiMIyR35d6 - clientSecret: infra-registry-okta/clientSecret - apiToken: infra-registry-okta/apiToken +# values.yaml +--- +config: + sources: [...] + groups: [...] + users: [...] +``` -groups: - - name: administrators - source: okta - roles: - - name: cluster-admin - kind: cluster-role - destinations: - - name: my-first-destination +See [Helm Chart reference](./helm.md) for a complete list of options configurable through Helm. + +Then, apply it to Infra: + +``` +helm -n infrahq upgrade -f values.yaml infra infrahq/infra +``` + +## Standalone Configuration + +First, create a config file `infra.yaml`: + +``` +sources: [...] +groups: [...] +users: [...] ``` -Then, apply it to the Infra Registry: +Then, apply it to Infra: ``` -helm upgrade infra-registry infrahq/registry -n infrahq --set-file config=./infra.yaml +helm -n infrahq upgrade --set-file=config=infra.yaml infra infrahq/infra ``` +## Reference + +### `sources` + +List of identity sources used to synchronize users and groups. + +| Parameter | Description | +|----------------|----------------------------------------------| +| `kind` | Source type | +| | Additional source-specific parameters | + +See [Identity Sources](./sources/) for a full list of configurable values. + +### `groups` + +List of groups to assign access. + +| Parameter | Description | +|----------------|----------------------------------------------| +| `name` | Group name as stored in the identity source | +| `roles` | Roles assigned to the user | + +### `users` + +List of users to assign access. + +| Parameter | Description | +|----------------|----------------------------------------------| +| `email` | User email as stored in the identity source | +| `roles` | Roles assigned to the user | + +### `roles` + +List of roles to assign to an user or group. + +| Parameter | Description | +|----------------|----------------------------------------------| +| `name` | Role name | +| `kind` | Role type | +| `destinations` | Destinations where this role binding applies | + +### `destinations` + +List of infrastructure destination to synchronize access permissions. + +| Parameter | Description | +|----------------|----------------------------------------------| +| `name` | Destination name | +| | Additional destination-specific parameters | + +See [Infrastructure Destinations](./destinations/) for a full list of configurable values. + ## Full Example ```yaml @@ -82,25 +135,3 @@ users: namespaces: - development ``` - -## Reference - -### `sources` - -A list of sources to sync and authenticate users from - -### `groups` - -A list of groups from identity providers for which to specify roles - -### `users` - -A list of users from identity providers for which to specify roles - -### `roles` - -`roles` is a list of role mappings to Kubernetes roles - -### `destinations` - -A list of Kubernetes clusters a role mapping applies to diff --git a/docs/destinations/kubernetes.md b/docs/destinations/kubernetes.md new file mode 100644 index 0000000000..a1c406bbc5 --- /dev/null +++ b/docs/destinations/kubernetes.md @@ -0,0 +1,63 @@ +# Destination / Kubernetes + +## Configure Kubernetes Destination + +### `destinations` + +| Parameter | Description | Default | +|----------------|--------------------------------------------------|-----------------------| +| `namespaces` | Limit access to only these Kubernetes namespaces | `[]` (all namespaces) | + +## Connect a Kubernetes Cluster + +Before installing Infra in additional clusters, you will first need to gather some information about your main Infra deployment. + +### Get Infra Endpoint + +Depending on your Infra Helm configurations, the steps will differ. + +
+ Ingress + + ``` + INFRA_HOST=$(kubectl -n infrahq get ingress -l infrahq.com/component=registry -o jsonpath="{.items[].status.loadBalancer.ingress[*]['ip', 'hostname']}") + ``` +
+ +
+ LoadBalancer + + Note: It may take a few minutes for the LoadBalancer endpoint to be assigned. You can watch the status of the service with: + + ``` + kubectl -n infrahq get services -l infrahq.com/component=registry -w + ``` + + ``` + INFRA_HOST=$(kubectl -n infrahq get services -l infrahq.com/component=registry -o jsonpath="{.items[].status.loadBalancer.ingress[*]['ip', 'hostname']}") + ``` +
+ +
+ ClusterIP + + ``` + CONTAINER_PORT=$(kubectl -n infrahq get services -l infrahq.com/component=registry -o jsonpath="{.items[].spec.ports[0].port}") + kubectl -n infrahq port-forward service/infra-registry 8080:$CONTAINER_PORT & + INFRA_HOST='localhost:8080' + ``` +
+ +### Get Infra API Key + +``` +INFRA_API_KEY=$(kubectl -n infrahq get secrets infra-registry -o jsonpath='{.data.engine-api-key}' | base64 --decode) +``` + +--- + +``` +helm install -n infrahq --create-namespace --set registry=$INFRA_HOST --set apiKey=$INFRA_API_KEY engine infrahq/engine +``` + +See [Helm Chart reference](./helm.md) for a complete list of options configurable through Helm. diff --git a/docs/helm.md b/docs/helm.md index 4d857b58b4..c9fecacb69 100644 --- a/docs/helm.md +++ b/docs/helm.md @@ -1,27 +1,27 @@ # Infra Helm Chart -The Infra helm chart is the recommended way of installing Infra on Kubernetes. +The Infra Helm chart is the recommended way of installing Infra on Kubernetes. -## Add Repo +## Add Helm Repo ``` helm repo add infrahq https://helm.infrahq.com helm repo update ``` -## Install the Infra Registry +## Install Infra ``` -helm install infra-registry infrahq/registry --namespace infrahq --create-namespace +helm install -n infrahq --create-namespace infra infrahq/infra ``` -## Advanced Load Balancer Configuration +## Advanced Service Configuration ### Internal Load Balancer ``` # example values.yaml - +--- service: annotations: # Google GKE @@ -40,7 +40,7 @@ service: ``` # example values.yaml - +--- service: type: ClusterIP annotations: @@ -58,7 +58,7 @@ service: ``` # example values.yaml - +--- service: type: ClusterIP @@ -75,11 +75,11 @@ ingress: alb.ingress.kubernetes.io/group.name: infra-registry # (optional: edit me to use an existing shared load balanacer) ``` -### `ingress-nginx` +### NGINX Ingress Controller ``` # example values.yaml - +--- service: type: ClusterIP @@ -95,53 +95,85 @@ ingress: cert-manager.io/issuer: "letsencrypt-prod" ``` +## Uninstall Infra + +``` +# Remove Infra +helm uninstall -n infrahq infra + +# Remove potential secrets created for Infra +kubectl delete -n infrahq secret/infra-registry-okta +``` + +## Uninstall Infra Engine + +``` +# Remove Infra Engine +helm uninstall -n infrahq infra-engine + +# Remove rolebindings & clusterrolebindings created by Infra Engine +kubectl delete clusterrolebindings,rolebindings -l app.kubernetes.io/managed-by=infra --all-namespaces +``` + ## Configuration Reference +### Infra + | Parameter | Description | Default | |------------------------------------|-----------------------------------------|------------------------------| -| `image.repository` | Image repository | `infrahq/registry` | -| `image.tag` | Image tag | Most recent version of Infra | -| `image.pullPolicy` | Image Pull Policy | `IfNotPresent` | -| `service.type` | Service type | `LoadBalancer` | -| `service.port` | Port to expose the plaintext service on | `80` | -| `service.targetPort` | Target plaintext container port | `80` | -| `service.portName` | Name of the plaintext service port | `plaintext` | -| `service.nodePort` | Service plaintext nodeport | `nil` | -| `service.tlsPort` | Port to expose the TLS service on | `443` | -| `service.tlsTargetPort` | Target TLS container port | `443` | -| `service.tlsPortName` | Name of the TLS service port | `tls` | -| `service.tlsNodePort` | Service TLS nodeport | `nil` | -| `service.annotations` | Service annotations | `{}` | -| `service.labels` | Service labels | `{}` | -| `service.loadBalancerIP` | IP address to assign to load balancer | `nil` | -| `service.loadBalancerSourceRanges` | List of IP CIDRs allowed access | `[]` | -| `service.externalIPs` | Service external IP addresses | `[]` | -| `service.clusterIP` | Internal cluster service IP | `nil` | +| `config` | Infra configuration file | `nil` | +| `crashReporting.enabled` | Enable crash report | `true` | +| `engine.*` | Engine chart values | (see below) | +| `engine.apiKey` | Engine API key | `""` (generated) | +| `telemetry.enabled` | Enable telemetry collection | `true` | +| `image.tag` | Image tag | `""` (latest release) | +| `image.repository` | Image repository | `infrahq/infra` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | | `ingress.enabled` | Enable ingress | `false` | | `ingress.host` | Ingress host | `""` | | `ingress.tls` | Ingress tls configuration | `[]` | | `ingress.servicePort` | Target http service port backend | `80` | | `ingress.annotations` | Ingress annotations (https) | `{}` | | `ingress.labels` | Ingress labels (https) | `{}` | +| `pod.annotations` | Pod annotations | `{}` | +| `service.type` | Service type | `LoadBalancer` | +| `service.ports` | Service ports | `[{name:http, port:80 targetPort:80}, {name:https, port:443, targetPort:43}]` | +| `service.ports[].port` | Service port | `nil` | +| `service.ports[].name` | Service port name | `nil` | +| `service.ports[].targetPort` | Service port target | `nil` | +| `service.ports[].protocol` | Service port protocol | `TCP` | +| `service.labels` | Service labels | `{}` | +| `service.annotations` | Service annotations | `{}` | +| `service.loadBalancerIP` | IP address to assign to load balancer | `nil` | +| `service.loadBalancerSourceRanges` | List of IP CIDRs allowed access | `[]` | +| `service.externalIPs` | Service external IP addresses | `[]` | +| `service.clusterIP` | Internal cluster service IP | `nil` | +| `serviceAccount.create` | Enable service account creation | `true` | +| `serviceAccount.annotations` | Service account annotations | `{}` | +| `storage` | Storage size | `1Gi` | -## Uninstalling - -Uninstall the Infra Registry - -``` -# Remove infra registry -helm uninstall infra-registry -n infrahq - -# Remove potential secrets created for infra registry -kubectl delete -n infrahq secret/infra-registry-okta -``` - -Uninstall the Infra Engine +### Infra Engine -``` -# Remove infra engine -helm uninstall infra-engine -n infrahq - -# Remove rolebindings & clusterrolebindings created by infra engine -kubectl delete clusterrolebindings,rolebindings -l app.kubernetes.io/managed-by=infra --all-namespaces -``` +| Parameter | Description | Default | +|------------------------------------|-----------------------------------------|------------------------------| +| `name` | Cluster name | `""` (auto-discovered) | +| `registry` | Infra Registry endpoint | `""` (required) | +| `apiKey` | Infra API key | `""` (required) | +| `image.tag` | Image tag | `""` (latest release) | +| `image.repository` | Image repository | `infrahq/infra` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `pod.annotations` | Pod annotations | `{}` | +| `service.type` | Service type | `LoadBalancer` | +| `service.ports` | Service ports | `[{name:https, port:443, targetPort:43}]` | +| `service.ports[].port` | Service port | `nil` | +| `service.ports[].name` | Service port name | `nil` | +| `service.ports[].targetPort` | Service port target | `nil` | +| `service.ports[].protocol` | Service port protocol | `TCP` | +| `service.labels` | Service labels | `{}` | +| `service.annotations` | Service annotations | `{}` | +| `service.loadBalancerIP` | IP address to assign to load balancer | `nil` | +| `service.loadBalancerSourceRanges` | List of IP CIDRs allowed access | `[]` | +| `service.externalIPs` | Service external IP addresses | `[]` | +| `service.clusterIP` | Internal cluster service IP | `nil` | +| `serviceAccount.create` | Enable service account creation | `true` | +| `serviceAccount.annotations` | Service account annotations | `{}` | diff --git a/docs/okta.md b/docs/sources/okta.md similarity index 68% rename from docs/okta.md rename to docs/sources/okta.md index 5c2069d242..b33a9435e7 100644 --- a/docs/okta.md +++ b/docs/sources/okta.md @@ -1,6 +1,18 @@ -# Okta +# Sources / Okta + +## Configure Okta Source + +| Parameter | Description | +|----------------|----------------------------------------------| +| `domain` | Okta domain | +| `clientId` | Okta client ID | +| `clientSecret` | Okta client secret | +| `apiToken` | Okta API token | + +## Connect an Okta Source This guide will walk you through the process of setting up Okta as an identity provider for Infra. At the end of this process you will have updated your Infra configuration with an Okta source that looks something like this: + ``` sources: - kind: okta @@ -10,24 +22,7 @@ sources: apiToken: infra-registry-okta/apiToken ``` -## Contents - -* [Prerequisites](#prerequisites) -* [Setup](#setup) - * [Create an Okta App](#create-an-okta-app) - * [Add Okta secrets to the Infra registry deployment](#add-okta-secrets-to-the-infra-registry-deployment) - * [Add Okta information to Infra registry](#add-okta-information-to-infra-registry) -* [Usage](#usage) - * [Login with Okta](#log-in-with-okta) - * [List Okta users](#list-okta-users) - -## Prerequisites - -* [Install Infra](../README.md#install) - -## Setup - -### Create an Okta App +## Create an Okta App 1. Login to the Okta administrative dashboard. 2. Under the left menu click **Applications > Applications**. Click **Create App Integration** then select **OIDC – OpenID Connect** and **Web Application**, and click **Next**. @@ -57,29 +52,50 @@ Create [Kubernetes Secret objects](https://kubernetes.io/docs/tasks/configmap-se #### Example Secret Creation Store the Okta client secret and API token on the same Kubernetes Secret object in the namespace that Infra registry is running in. ``` -kubectl create secret generic infra-registry-okta \ ---namespace=infrahq \ ---from-literal=clientSecret=jfpn0qwiQPiMIfs408fjs048fjpn0qwiQPiMajsdf08j10j2 \ ---from-literal=apiToken=001XJv9xhv899sdfns938haos3h8oahsdaohd2o8hdao82hd +OKTA_CLIENT_SECRET=jfpn0qwiQPiMIfs408fjs048fjpn0qwiQPiMajsdf08j10j2 +OKTA_API_TOKEN=001XJv9xhv899sdfns938haos3h8oahsdaohd2o8hdao82hd +kubectl -n infrahq create secret generic infra-registry-okta --from-literal=clientSecret=$OKTA_CLIENT_SECRET --from-literal=apiToken=$OKTA_API_TOKEN ``` -### Add Okta information to Infra registry config +## Add Okta Information to Infra Configuration Edit your [Infra configuration](./configuration.md) (e.g. `infra.yaml`) to include an Okta source: ```yaml +# infra.yaml +--- sources: - kind: okta - domain: acme.okta.com + domain: example.okta.com clientId: 0oapn0qwiQPiMIyR35d6 - clientSecret: infra-registry-okta/clientSecret # / + clientSecret: infra-registry-okta/clientSecret # / apiToken: infra-registry-okta/apiToken ``` Then apply this config change: ``` -helm upgrade infra-registry infrahq/registry --set-file config=./infra.yaml -n infrahq +helm -n infrahq upgrade --set-file config=infra.yaml infra infrahq/infra +``` + +Infra configuration can also be added to Helm values: + +```yaml +# values.yaml +--- +config: + sources: + - kind: okta + domain: example.okta.com + clientId: 0oapn0qwiQPiMIyR35d6 + clientSecret: infra-registry-okta/clientSecret # / + apiToken: infra-registry-okta/apiToken +``` + +Then apply this config change: + +``` +helm -n infrahq upgrade -f values.yaml infra infrahq/infra ``` ### Login with Okta diff --git a/helm/charts/engine/templates/_helpers.tpl b/helm/charts/engine/templates/_helpers.tpl index 50c8de8bca..05208b6f70 100644 --- a/helm/charts/engine/templates/_helpers.tpl +++ b/helm/charts/engine/templates/_helpers.tpl @@ -34,6 +34,7 @@ Create chart name and version as used by the chart label. Common labels */}} {{- define "engine.labels" -}} +infrahq.com/component: engine helm.sh/chart: {{ include "engine.chart" . }} {{ include "engine.selectorLabels" . }} {{- if .Chart.AppVersion }} diff --git a/helm/charts/engine/templates/deployment.yaml b/helm/charts/engine/templates/deployment.yaml index e5d5235f20..0f8a9a5f5a 100644 --- a/helm/charts/engine/templates/deployment.yaml +++ b/helm/charts/engine/templates/deployment.yaml @@ -39,38 +39,38 @@ spec: containerPort: {{ .targetPort }} {{- end }} env: -{{- with .Values.name }} - - name: INFRA_ENGINE_NAME - value: {{ . | quote }} -{{- end }} - - name: INFRA_ENGINE_REGISTRY - value: {{ required "Value registry is required" .Values.registry | quote }} - name: INFRA_LOG_LEVEL value: {{ .Values.logLevel | default "info" }} - - name: ENGINE_API_KEY - valueFrom: - secretKeyRef: - name: infra-engine - key: api-key - optional: true + - name: INFRA_ENGINE_NAME + value: {{ .Values.name | quote }} + - name: INFRA_ENGINE_REGISTRY + value: {{ .Values.registry | quote }} + - name: INFRA_ENGINE_API_KEY + value: 'file:///var/run/secrets/infrahq.com/engine/engine-key' volumeMounts: - name: data mountPath: /var/run/infra/data + - name: secret-engine + mountPath: /var/run/secrets/infrahq.com/engine + readOnly: true readinessProbe: httpGet: - scheme: HTTPS - path: /healthz {{- with first .Values.service.ports }} + scheme: {{ .name | upper }} + path: /healthz port: {{ .targetPort }} {{- end }} livenessProbe: httpGet: - scheme: HTTPS - path: /healthz {{- with first .Values.service.ports }} + scheme: {{ .name | upper }} + path: /healthz port: {{ .targetPort }} {{- end }} periodSeconds: 3 volumes: - name: data emptyDir: {} + - name: secret-engine + secret: + secretName: infra-engine diff --git a/helm/charts/engine/templates/secret.yaml b/helm/charts/engine/templates/secret.yaml index a32414f24a..5ba2967ea4 100644 --- a/helm/charts/engine/templates/secret.yaml +++ b/helm/charts/engine/templates/secret.yaml @@ -1,9 +1,12 @@ +{{- if .Values.apiKey }} +--- apiVersion: v1 kind: Secret metadata: - name: {{ include "engine.fullname" . }} + name: infra-engine labels: {{- include "engine.labels" . | nindent 4 }} type: Opaque data: - api-key: {{ .Values.apiKey | b64enc | quote }} + engine-key: {{ .Values.apiKey | b64enc | quote }} +{{- end }} diff --git a/helm/charts/registry/.helmignore b/helm/charts/infra/.helmignore similarity index 100% rename from helm/charts/registry/.helmignore rename to helm/charts/infra/.helmignore diff --git a/helm/charts/registry/Chart.yaml b/helm/charts/infra/Chart.yaml similarity index 72% rename from helm/charts/registry/Chart.yaml rename to helm/charts/infra/Chart.yaml index de753c4770..b9ebac7c3a 100644 --- a/helm/charts/registry/Chart.yaml +++ b/helm/charts/infra/Chart.yaml @@ -1,6 +1,9 @@ apiVersion: v2 -name: registry +name: infra description: Infra Registry type: application version: 0.0.0-development appVersion: 0.0.0-development + +dependencies: + - name: engine diff --git a/helm/charts/infra/templates/NOTES.txt b/helm/charts/infra/templates/NOTES.txt new file mode 100644 index 0000000000..1c70a7c375 --- /dev/null +++ b/helm/charts/infra/templates/NOTES.txt @@ -0,0 +1,35 @@ +************************************************* + + Welcome to Infra + + Login to this cluster with infra-cli: https://github.com/infrahq/infra#install-infra-cli + +{{- if .Values.ingress.enabled }} +{{- with first .Values.ingress.hosts }} + $ INFRA_HOST={{ . }} +{{- end }} +{{- else if eq .Values.service.type "NodePort" }} + $ NODE_HOST=$(kubectl get nodes -o jsonpath="{.items[].status.addresses[].address}") + $ NODE_PORT=$(kubectl -n {{ .Release.Namespace }} get services -l infrahq.com/component=registry -o jsonpath="{.items[].spec.ports[0].nodePort}") + $ INFRA_HOST=$NODE_HOST:$NODE_PORT +{{- else if eq .Values.service.type "LoadBalancer" }} + + NOTE: It may take a few minutes for the LoadBalancer address to be assigned. + You can watch the status of by running 'kubectl -n {{ .Release.Namespace }} get services -l infrahq.com/component=registry -w' + + $ INFRA_HOST=$(kubectl -n {{ .Release.Namespace }} get services -l infrahq.com/component=registry -o jsonpath="{.items[].status.loadBalancer.ingress[*]['ip', 'hostname']}") +{{- else if contains .Values.service.type "ClusterIP" }} + $ CONTAINER_PORT=$(kubectl -n {{ .Release.Namespace }} get services -l infrahq.com/component=registry -o jsonpath="{.items[].spec.ports[0].port}") + $ echo "Visit http://127.0.0.1:8080 to use your application" + $ kubectl -n {{ .Release.Namespace }} port-forward service/infra-registry 8080:$CONTAINER_PORT +{{- end }} + $ INFRA_ENGINE_API_KEY=$(kubectl --namespace {{ .Release.Namespace }} get secrets infra-registry --template={{ "{{" }}.data.engineApiKey{{ "}}" }} | base64 -D) + + $ infra login $INFRA_HOST + + Next steps: + + * Connect additional identity sources: https://github.com/infrahq/infra/blob/main/docs/sources/ + * Connect additional infrastructure destinations: https://github.com/infrahq/infra/blob/main/docs/destinations/ + +************************************************* diff --git a/helm/charts/registry/templates/_helpers.tpl b/helm/charts/infra/templates/_helpers.tpl similarity index 75% rename from helm/charts/registry/templates/_helpers.tpl rename to helm/charts/infra/templates/_helpers.tpl index 0ed5df43ad..5640f06322 100644 --- a/helm/charts/registry/templates/_helpers.tpl +++ b/helm/charts/infra/templates/_helpers.tpl @@ -1,7 +1,7 @@ {{/* Expand the name of the chart. */}} -{{- define "registry.name" -}} +{{- define "infra.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} @@ -10,7 +10,7 @@ Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} -{{- define "registry.fullname" -}} +{{- define "infra.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -26,16 +26,17 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "registry.chart" -}} +{{- define "infra.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} -{{- define "registry.labels" -}} -helm.sh/chart: {{ include "registry.chart" . }} -{{ include "registry.selectorLabels" . }} +{{- define "infra.labels" -}} +infrahq.com/component: registry +helm.sh/chart: {{ include "infra.chart" . }} +{{ include "infra.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -45,17 +46,17 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Selector labels */}} -{{- define "registry.selectorLabels" -}} -app.kubernetes.io/name: {{ include "registry.name" . }} +{{- define "infra.selectorLabels" -}} +app.kubernetes.io/name: {{ include "infra.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} -{{- define "registry.serviceAccountName" -}} +{{- define "infra.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} -{{- default (include "registry.fullname" .) .Values.serviceAccount.name }} +{{- default (include "infra.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} diff --git a/helm/charts/registry/templates/configmap.yaml b/helm/charts/infra/templates/configmap.yaml similarity index 76% rename from helm/charts/registry/templates/configmap.yaml rename to helm/charts/infra/templates/configmap.yaml index d90b129128..282b56e8d1 100644 --- a/helm/charts/registry/templates/configmap.yaml +++ b/helm/charts/infra/templates/configmap.yaml @@ -2,9 +2,9 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} labels: -{{- include "registry.labels" . | nindent 4 }} +{{- include "infra.labels" . | nindent 4 }} data: {{- if kindIs "map" .Values.config }} infra.yaml: | diff --git a/helm/charts/registry/templates/deployment.yaml b/helm/charts/infra/templates/deployment.yaml similarity index 65% rename from helm/charts/registry/templates/deployment.yaml rename to helm/charts/infra/templates/deployment.yaml index 490434ab68..40cf2406ba 100644 --- a/helm/charts/registry/templates/deployment.yaml +++ b/helm/charts/infra/templates/deployment.yaml @@ -1,18 +1,18 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} labels: -{{- include "registry.labels" . | nindent 4 }} +{{- include "infra.labels" . | nindent 4 }} spec: selector: matchLabels: -{{- include "registry.selectorLabels" . | nindent 6 }} +{{- include "infra.selectorLabels" . | nindent 6 }} replicas: 1 template: metadata: labels: -{{- include "registry.selectorLabels" . | nindent 8 }} +{{- include "infra.selectorLabels" . | nindent 8 }} annotations: rollme: {{ randAlphaNum 5 | quote }} {{- with .Values.pod.annotations }} @@ -20,7 +20,7 @@ spec: {{- end }} spec: automountServiceAccountToken: true - serviceAccountName: {{ include "registry.serviceAccountName" . }} + serviceAccountName: {{ include "infra.serviceAccountName" . }} dnsPolicy: ClusterFirst {{- with .Values.image.pullSecrets }} imagePullSecrets: @@ -45,30 +45,30 @@ spec: - "--enable-crash-reporting" - {{ .Values.crashReporting.enabled | quote }} env: - - name: INFRA_REGISTRY_ROOT_API_KEY - valueFrom: - secretKeyRef: - name: infra-registry - key: rootApiKey - optional: true - - name: ENGINE_API_KEY - valueFrom: - secretKeyRef: - name: infra-registry - key: engineApiKey - optional: true - name: INFRA_LOG_LEVEL value: {{ .Values.logLevel | default "info" }} - name: INFRA_SYNC_INTERVAL_SECONDS value: "30" + - name: INFRA_ROOT_API_KEY + value: 'file:///var/run/secrets/infrahq.com/registry/root-key' + - name: INFRA_ENGINE_API_KEY + value: 'file:///var/run/secrets/infrahq.com/engine/engine-key' ports: - - containerPort: 80 - - containerPort: 443 +{{- range .Values.service.ports }} + - name: {{ .name }} + containerPort: {{ .targetPort }} +{{- end }} volumeMounts: - - name: data - mountPath: /var/run/infra/data - name: config mountPath: /var/run/infra/config + - name: data + mountPath: /var/run/infra/data + - name: secret-registry + mountPath: /var/run/secrets/infrahq.com/registry + readOnly: true + - name: secret-engine + mountPath: /var/run/secrets/infrahq.com/engine + readOnly: true readinessProbe: httpGet: {{- with first .Values.service.ports }} @@ -87,8 +87,14 @@ spec: volumes: - name: config configMap: - name: infra-registry + name: {{ include "infra.fullname" . }} optional: true - name: data persistentVolumeClaim: - claimName: infra-registry + claimName: {{ include "infra.fullname" . }} + - name: secret-registry + secret: + secretName: infra-registry + - name: secret-engine + secret: + secretName: infra-engine diff --git a/helm/charts/registry/templates/ingress.yaml b/helm/charts/infra/templates/ingress.yaml similarity index 93% rename from helm/charts/registry/templates/ingress.yaml rename to helm/charts/infra/templates/ingress.yaml index 95426b5897..bae0019486 100644 --- a/helm/charts/registry/templates/ingress.yaml +++ b/helm/charts/infra/templates/ingress.yaml @@ -11,9 +11,9 @@ apiVersion: {{ $apiVersion }} kind: Ingress metadata: - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} labels: -{{- include "registry.labels" . | nindent 4 }} +{{- include "infra.labels" . | nindent 4 }} {{- with .Values.ingress.labels }} {{- toYaml . | nindent 4 }} {{- end }} diff --git a/helm/charts/registry/templates/pvc.yaml b/helm/charts/infra/templates/pvc.yaml similarity index 64% rename from helm/charts/registry/templates/pvc.yaml rename to helm/charts/infra/templates/pvc.yaml index f3dbe33f01..50120fef40 100644 --- a/helm/charts/registry/templates/pvc.yaml +++ b/helm/charts/infra/templates/pvc.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} labels: -{{- include "registry.labels" . | nindent 4 }} +{{- include "infra.labels" . | nindent 4 }} spec: accessModes: - ReadWriteOnce diff --git a/helm/charts/registry/templates/role.yaml b/helm/charts/infra/templates/role.yaml similarity index 61% rename from helm/charts/registry/templates/role.yaml rename to helm/charts/infra/templates/role.yaml index 352ad82241..29caf17d13 100644 --- a/helm/charts/registry/templates/role.yaml +++ b/helm/charts/infra/templates/role.yaml @@ -1,9 +1,9 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} labels: -{{- include "registry.labels" . | nindent 4 }} +{{- include "infra.labels" . | nindent 4 }} rules: - apiGroups: [""] resources: ["secrets"] diff --git a/helm/charts/registry/templates/rolebinding.yaml b/helm/charts/infra/templates/rolebinding.yaml similarity index 54% rename from helm/charts/registry/templates/rolebinding.yaml rename to helm/charts/infra/templates/rolebinding.yaml index 7cb4a95c25..33832ff993 100644 --- a/helm/charts/registry/templates/rolebinding.yaml +++ b/helm/charts/infra/templates/rolebinding.yaml @@ -1,14 +1,14 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} labels: -{{- include "registry.labels" . | nindent 4 }} +{{- include "infra.labels" . | nindent 4 }} subjects: - kind: ServiceAccount - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} namespace: {{ .Release.Namespace }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} diff --git a/helm/charts/infra/templates/secret.yaml b/helm/charts/infra/templates/secret.yaml new file mode 100644 index 0000000000..ee37e79859 --- /dev/null +++ b/helm/charts/infra/templates/secret.yaml @@ -0,0 +1,38 @@ +{{- define "root.key" }} +{{- $fullname := include "infra.fullname" . }} +{{- $secret := lookup "v1" "Secret" .Release.Namespace "infra" }} + {{- if $secret }} + {{- index $secret "data" "root-key" | b64dec }} + {{- else }} + {{- randAlphaNum 24 }} + {{- end }} +{{- end }} +{{- define "engine.key" }} +{{- $fullname := include "infra.fullname" . }} +{{- $secret := lookup "v1" "Secret" .Release.Namespace "infra-engine" }} + {{- if $secret }} + {{- index $secret "data" "engine-key" | b64dec }} + {{- else }} + {{- randAlphaNum 24 }} +{{- end }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: infra-registry + labels: +{{- include "infra.labels" . | nindent 4 }} +type: Opaque +data: + root-key: {{ include "root.key" . | b64enc | quote }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: infra-engine + labels: +{{- include "infra.labels" . | nindent 4 }} +type: Opaque +data: + engine-key: {{ include "engine.key" . | b64enc | quote }} diff --git a/helm/charts/registry/templates/service.yaml b/helm/charts/infra/templates/service.yaml similarity index 89% rename from helm/charts/registry/templates/service.yaml rename to helm/charts/infra/templates/service.yaml index ebfb07998f..4acc01c0e7 100644 --- a/helm/charts/registry/templates/service.yaml +++ b/helm/charts/infra/templates/service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "registry.fullname" . }} + name: {{ include "infra.fullname" . }} labels: -{{- include "registry.labels" . | nindent 4 }} +{{- include "infra.labels" . | nindent 4 }} {{- with .Values.service.labels }} {{- toYaml . | nindent 4 }} {{- end }} @@ -46,4 +46,4 @@ spec: {{- end }} {{- end }} selector: -{{- include "registry.selectorLabels" . | nindent 6 }} +{{- include "infra.selectorLabels" . | nindent 6 }} diff --git a/helm/charts/infra/templates/serviceaccount.yaml b/helm/charts/infra/templates/serviceaccount.yaml new file mode 100644 index 0000000000..7462440500 --- /dev/null +++ b/helm/charts/infra/templates/serviceaccount.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "infra.fullname" . }} + labels: +{{- include "infra.labels" . | nindent 4 }} diff --git a/helm/charts/registry/values.yaml b/helm/charts/infra/values.yaml similarity index 95% rename from helm/charts/registry/values.yaml rename to helm/charts/infra/values.yaml index 9d8d0dcdb5..05689aaef8 100644 --- a/helm/charts/registry/values.yaml +++ b/helm/charts/infra/values.yaml @@ -1,6 +1,8 @@ -storage: 1Gi -engineApiKey: "" config: null +storage: 1Gi + +engine: + apiKey: "" pod: annotations: {} diff --git a/helm/charts/registry/templates/NOTES.txt b/helm/charts/registry/templates/NOTES.txt deleted file mode 100644 index c121685972..0000000000 --- a/helm/charts/registry/templates/NOTES.txt +++ /dev/null @@ -1,33 +0,0 @@ -************************************************* - - Welcome to Infra - - To continue, run: - -{{- if .Values.ingress.enabled }} -{{- with first .Values.ingress.hosts }} - export INFRA_REGISTRY={{ . }} -{{- end }} -{{- else if eq .Values.service.type "NodePort" }} - export NODE_PORT=$(kubectl --namespace {{ .Release.Namespace }} get services infra-registry -o jsonpath="{.spec.ports[0].nodePort}") - export NODE_ADDR=$(kubectl --namespace {{ .Release.Namespace }} get nodes -o jsonpath="{.items[0].status.addresses[0].address}") - export INFRA_REGISTRY=$NODE_ADDR:$NODE_PORT -{{- else if eq .Values.service.type "LoadBalancer" }} - - NOTE: It may take a few minutes for the LoadBalancer address to be assigned. - You can watch the status of by running 'kubectl --namespace {{ .Release.Namespace }} get services infra-registry --watch' - - export INFRA_REGISTRY=$(kubectl --namespace {{ .Release.Namespace }} get services infra-registry -o jsonpath="{.status.loadBalancer.ingress[*]['ip', 'hostname']}") -{{- else if contains .Values.service.type "ClusterIP" }} - SERVICE_PORT=$(kubectl --namespace {{ .Release.Namespace }} get services infra-registry -o jsonpath="{.spec.ports[0].port}") - echo "Visit http://127.0.0.1:8080 to use your application" - kubectl --namespace {{ .Release.Namespace }} port-forward service/infra-registry 8080:$SERVICE_PORT -{{- end }} - export ENGINE_API_KEY=$(kubectl --namespace {{ .Release.Namespace }} get secrets infra-registry --template={{ "{{" }}.data.engineApiKey{{ "}}" }} | base64 -D) - - To connect your first cluster, run: - - helm install infra-engine infrahq/engine -n infrahq --set registry=$INFRA_REGISTRY --set apiKey=$ENGINE_API_KEY - - -************************************************* diff --git a/helm/charts/registry/templates/secret.yaml b/helm/charts/registry/templates/secret.yaml deleted file mode 100644 index 70a6da6c6a..0000000000 --- a/helm/charts/registry/templates/secret.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- define "rootApiKey" }} -{{- $secret := lookup "v1" "Secret" .Release.Namespace "infra-registry" }} - {{- if $secret }} - {{- index $secret "data" "rootApiKey" | b64dec }} - {{- else }} - {{- randAlphaNum 24 }} - {{- end }} -{{- end }} -{{- define "engineApiKey" }} -{{- $secret := lookup "v1" "Secret" .Release.Namespace "infra-registry" }} - {{- if $secret }} - {{- index $secret "data" "engineApiKey" | b64dec }} - {{- else }} - {{- randAlphaNum 24 }} - {{- end }} -{{- end }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "registry.fullname" . }} - labels: -{{- include "registry.labels" . | nindent 4 }} -type: Opaque -data: - rootApiKey: {{ include "rootApiKey" . | b64enc | quote }} -{{- if .Values.engineApiKey }} - engineApiKey: {{ .Values.engineApiKey | b64enc | quote }} -{{- else }} - engineApiKey: {{ include "engineApiKey" . | b64enc | quote }} -{{- end }} diff --git a/helm/charts/registry/templates/serviceaccount.yaml b/helm/charts/registry/templates/serviceaccount.yaml deleted file mode 100644 index f2ca42f86d..0000000000 --- a/helm/charts/registry/templates/serviceaccount.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "registry.fullname" . }} - labels: -{{- include "registry.labels" . | nindent 4 }} diff --git a/internal/api/configuration.go b/internal/api/configuration.go index 74e739c786..b1168437ca 100644 --- a/internal/api/configuration.go +++ b/internal/api/configuration.go @@ -77,9 +77,9 @@ type ServerVariable struct { // ServerConfiguration stores the information about a server type ServerConfiguration struct { - URL string + URL string Description string - Variables map[string]ServerVariable + Variables map[string]ServerVariable } // ServerConfigurations stores multiple ServerConfiguration items @@ -100,17 +100,16 @@ type Configuration struct { // NewConfiguration returns a new Configuration object func NewConfiguration() *Configuration { cfg := &Configuration{ - DefaultHeader: make(map[string]string), - UserAgent: "OpenAPI-Generator/1.0.0/go", - Debug: false, - Servers: ServerConfigurations{ + DefaultHeader: make(map[string]string), + UserAgent: "OpenAPI-Generator/1.0.0/go", + Debug: false, + Servers: ServerConfigurations{ { - URL: "/v1", + URL: "/v1", Description: "No description provided", }, }, - OperationServers: map[string]ServerConfigurations{ - }, + OperationServers: map[string]ServerConfigurations{}, } return cfg } diff --git a/internal/api/response.go b/internal/api/response.go index 8bae58f4cb..ed46fd1326 100644 --- a/internal/api/response.go +++ b/internal/api/response.go @@ -34,14 +34,12 @@ type APIResponse struct { // NewAPIResponse returns a new APIResponse object. func NewAPIResponse(r *http.Response) *APIResponse { - response := &APIResponse{Response: r} return response } // NewAPIResponseWithError returns a new APIResponse object with the provided error message. func NewAPIResponseWithError(errorMessage string) *APIResponse { - response := &APIResponse{Message: errorMessage} return response } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index beb12dd602..73c4fb11d8 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -3,7 +3,6 @@ package cmd import ( "context" "crypto/tls" - "errors" "fmt" "net/http" "os" @@ -264,8 +263,8 @@ func newRegistryCmd() (*cobra.Command, error) { defaultInfraHome := filepath.Join("~", ".infra") cmd.Flags().StringVarP(&options.ConfigPath, "config", "c", "", "config file") - cmd.Flags().StringVar(&options.RootAPIKey, "root-api-key", os.Getenv("INFRA_REGISTRY_ROOT_API_KEY"), "the root api key for privileged actions") - cmd.Flags().StringVar(&options.EngineApiKey, "engine-api-key", os.Getenv("ENGINE_API_KEY"), "initial api key for adding destinations") + cmd.Flags().StringVar(&options.RootApiKey, "root-api-key", os.Getenv("INFRA_ROOT_API_KEY"), "root API key") + cmd.Flags().StringVar(&options.EngineApiKey, "engine-api-key", os.Getenv("INFRA_ENGINE_API_KEY"), "engine registration API key") cmd.Flags().StringVar(&options.DBPath, "db", filepath.Join(defaultInfraHome, "infra.db"), "path to database file") cmd.Flags().StringVar(&options.TLSCache, "tls-cache", filepath.Join(defaultInfraHome, "cache"), "path to directory to cache tls self-signed and Let's Encrypt certificates") cmd.Flags().BoolVar(&options.UI, "ui", false, "enable ui") @@ -309,11 +308,13 @@ func newEngineCmd() (*cobra.Command, error) { Short: "Start Infra Engine", RunE: func(cmd *cobra.Command, args []string) error { if options.Registry == "" { - return errors.New("registry not specified (--registry or INFRA_ENGINE_REGISTRY)") + logging.L.Warn("registry not specified; will try to get from Kubernetes") } - if options.Registry != "infra" && options.APIKey == "" { - return errors.New("api-key not specified (--api-key or ENGINE_API_KEY)") + + if options.EngineApiKey == "" { + return fmt.Errorf("'--engine-api-key' not specified") } + return engine.Run(options) }, } @@ -324,7 +325,7 @@ func newEngineCmd() (*cobra.Command, error) { cmd.Flags().StringVarP(&options.Registry, "registry", "r", os.Getenv("INFRA_ENGINE_REGISTRY"), "registry hostname") cmd.Flags().StringVarP(&options.Name, "name", "n", os.Getenv("INFRA_ENGINE_NAME"), "cluster name") cmd.Flags().StringVar(&options.TLSCache, "tls-cache", filepath.Join(defaultInfraHome, "cache"), "path to directory to cache tls self-signed and Let's Encrypt certificates") - cmd.Flags().StringVar(&options.APIKey, "api-key", os.Getenv("ENGINE_API_KEY"), "api key") + cmd.Flags().StringVar(&options.EngineApiKey, "engine-api-key", os.Getenv("INFRA_ENGINE_API_KEY"), "engine registration API key") if filepath.Dir(options.TLSCache) == defaultInfraHome { infraDir, err := infraHomeDir() diff --git a/internal/engine/engine.go b/internal/engine/engine.go index cd218bb87c..44f882f940 100644 --- a/internal/engine/engine.go +++ b/internal/engine/engine.go @@ -34,7 +34,7 @@ type Options struct { Registry string Name string ForceTLSVerify bool - APIKey string + EngineApiKey string TLSCache string } @@ -237,6 +237,45 @@ func Run(options Options) error { } } + engineApiKeyURI, err := url.Parse(options.EngineApiKey) + if err != nil { + return err + } + + var engineApiKey string + + switch engineApiKeyURI.Scheme { + case "": + // option does not have a scheme, assume it is plaintext + engineApiKey = string(options.EngineApiKey) + case "file": + // option is a file path, read contents from the path + contents, err := ioutil.ReadFile(engineApiKeyURI.Path) + if err != nil { + return err + } + + engineApiKey = string(contents) + + default: + return fmt.Errorf("unsupported secret format %s", engineApiKeyURI.Scheme) + } + + k8s, err := kubernetes.NewKubernetes() + if err != nil { + return err + } + + if options.Registry == "" { + registry, err := k8s.Service("registry") + if err != nil { + return err + } + + metadata := registry.ObjectMeta + options.Registry = fmt.Sprintf("%s.%s", metadata.Name, metadata.Namespace) + } + u, err := urlx.Parse(options.Registry) if err != nil { return err @@ -245,7 +284,7 @@ func Run(options Options) error { u.Scheme = "https" ctx := context.WithValue(context.Background(), api.ContextServerVariables, map[string]string{"basePath": "v1"}) - ctx = context.WithValue(ctx, api.ContextAccessToken, options.APIKey) + ctx = context.WithValue(ctx, api.ContextAccessToken, engineApiKey) config := api.NewConfiguration() config.Host = u.Host config.Scheme = "https" @@ -257,11 +296,6 @@ func Run(options Options) error { client := api.NewAPIClient(config) - k8s, err := kubernetes.NewKubernetes() - if err != nil { - return err - } - name := options.Name if name == "" { name, err = k8s.Name() @@ -375,7 +409,7 @@ func Run(options Options) error { cache := jwkCache{ client: &http.Client{ Transport: &BearerTransport{ - Token: options.APIKey, + Token: engineApiKey, Transport: &http.Transport{ TLSClientConfig: registryTLSConfig, }, diff --git a/internal/kubernetes/kubernetes.go b/internal/kubernetes/kubernetes.go index 33decc34ed..842b7ef838 100644 --- a/internal/kubernetes/kubernetes.go +++ b/internal/kubernetes/kubernetes.go @@ -20,6 +20,7 @@ import ( "github.com/infrahq/infra/secrets" "github.com/jessevdk/go-flags" "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -634,30 +635,38 @@ func (k *Kubernetes) CA() ([]byte, error) { return contents, nil } -// Find a suitable Endpoint to use by inspecting the engine's Service objects -func (k *Kubernetes) Endpoint() (string, error) { +// Find the first suitable Service, filtering on infrahq.com/component +func (k *Kubernetes) Service(component string) (*corev1.Service, error) { clientset, err := kubernetes.NewForConfig(k.Config) if err != nil { - return "", err + return nil, err } namespace, err := k.Namespace() if err != nil { - return "", err + return nil, err } - services, err := clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: "app.kubernetes.io/instance=infra-engine", + services, err := clientset.CoreV1().Services(namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: fmt.Sprintf("infrahq.com/component=%s", component), }) if err != nil { - return "", err + return nil, err } if len(services.Items) == 0 { - return "", errors.New("no services found") + return nil, fmt.Errorf("no service found for component %s", component) } - service := services.Items[0] + return &services.Items[0], nil +} + +// Find a suitable Endpoint to use by inspecting the engine's Service objects +func (k *Kubernetes) Endpoint() (string, error) { + service, err := k.Service("engine") + if err != nil { + return "", err + } if len(service.Status.LoadBalancer.Ingress) == 0 { return "", errors.New("load balancer has no ingress objects") diff --git a/internal/registry/api.go b/internal/registry/api.go index 1819c32970..52df2f844e 100644 --- a/internal/registry/api.go +++ b/internal/registry/api.go @@ -560,7 +560,7 @@ func (a *Api) CreateAPIKey(w http.ResponseWriter, r *http.Request) { return } - if strings.ToLower(body.Name) == engineApiKeyName || strings.ToLower(body.Name) == rootAPIKeyName { + if strings.ToLower(body.Name) == engineApiKeyName || strings.ToLower(body.Name) == rootApiKeyName { // this name is used for the default API key that engines use to connect to the registry sendApiError(w, http.StatusBadRequest, fmt.Sprintf("cannot create an API key with the name %s, this name is reserved", body.Name)) return diff --git a/internal/registry/registry.go b/internal/registry/registry.go index a80917b4c7..7362a20a7a 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -5,12 +5,15 @@ package registry import ( "errors" + "fmt" "io/fs" "io/ioutil" "net/http" "net/http/httputil" + "net/url" "os" "strconv" + "strings" "time" "github.com/NYTimes/gziphandler" @@ -32,7 +35,7 @@ import ( type Options struct { DBPath string TLSCache string - RootAPIKey string + RootApiKey string EngineApiKey string ConfigPath string UI bool @@ -43,7 +46,7 @@ type Options struct { } const ( - rootAPIKeyName = "root" + rootApiKeyName = "root" engineApiKeyName = "engine" ) @@ -230,46 +233,80 @@ func Run(options Options) error { defer telemetryTimer.Stop() - if options.RootAPIKey != "" { - if len(options.RootAPIKey) != ApiKeyLen { - return errors.New("invalid root api key length, the key must be 24 characters") - } + var rootApiKey ApiKey + if err := db.FirstOrCreate(&rootApiKey, &ApiKey{Name: rootApiKeyName}).Error; err != nil { + return err + } - var rootAPIKey ApiKey + rootApiKeyURI, err := url.Parse(options.RootApiKey) + if err != nil { + return err + } - err = db.FirstOrCreate(&rootAPIKey, &ApiKey{Name: rootAPIKeyName}).Error + switch rootApiKeyURI.Scheme { + case "": + // option does not have a scheme, assume it is plaintext + rootApiKey.Key = string(options.RootApiKey) + case "file": + // option is a file path, read contents from the path + contents, err = ioutil.ReadFile(rootApiKeyURI.Path) if err != nil { return err } - rootAPIKey.Permissions = string(api.STAR) - rootAPIKey.Key = options.RootAPIKey + rootApiKey.Key = string(contents) - err := db.Save(&rootAPIKey).Error - if err != nil { - return err - } + default: + return fmt.Errorf("unsupported secret format %s", rootApiKeyURI.Scheme) } - if options.EngineApiKey != "" { - if len(options.EngineApiKey) != ApiKeyLen { - return errors.New("invalid engine api key length, the key must be 24 characters") - } + if len(contents) != ApiKeyLen { + return fmt.Errorf("invalid api key length, the key must be 24 characters") + } - var engineApiKey ApiKey + rootApiKey.Permissions = string(api.STAR) - err = db.FirstOrCreate(&engineApiKey, &ApiKey{Name: engineApiKeyName}).Error - if err != nil { - return err - } + if err := db.Save(&rootApiKey).Error; err != nil { + return err + } + + var engineApiKey ApiKey + if err := db.FirstOrCreate(&engineApiKey, &ApiKey{Name: engineApiKeyName}).Error; err != nil { + return err + } - engineApiKey.Permissions = string(api.DESTINATIONS_CREATE) + " " + string(api.ROLES_READ) - engineApiKey.Key = options.EngineApiKey + engineApiKeyURI, err := url.Parse(options.EngineApiKey) + if err != nil { + return err + } - err := db.Save(&engineApiKey).Error + switch engineApiKeyURI.Scheme { + case "": + // option does not have a scheme, assume it is plaintext + engineApiKey.Key = string(options.EngineApiKey) + case "file": + // option is a file path, read contents from the path + contents, err := ioutil.ReadFile(engineApiKeyURI.Path) if err != nil { return err } + + engineApiKey.Key = string(contents) + default: + return fmt.Errorf("unsupported secret format %s", engineApiKeyURI.Scheme) + } + + if len(contents) != ApiKeyLen { + return fmt.Errorf("invalid api key length, the key must be 24 characters") + } + + engineApiKey.Permissions = strings.Join([]string{ + string(api.ROLES_READ), + string(api.DESTINATIONS_CREATE), + }, " ") + + if err := db.Save(&engineApiKey).Error; err != nil { + return err } if err := os.MkdirAll(options.TLSCache, os.ModePerm); err != nil {