diff --git a/.github/workflows/terraform-docs.yaml b/.github/workflows/terraform-docs.yaml index c016add..84c758c 100644 --- a/.github/workflows/terraform-docs.yaml +++ b/.github/workflows/terraform-docs.yaml @@ -7,3 +7,5 @@ on: jobs: terraform-docs: uses: camptocamp/devops-stack/.github/workflows/modules-terraform-docs.yaml@master + with: + variants: "oidc_bootstrap" diff --git a/README.adoc b/README.adoc index 6c8161e..9dc754f 100644 --- a/README.adoc +++ b/README.adoc @@ -1,19 +1,121 @@ += devops-stack-module-keycloak +// Document attributes to replace along the document +:keycloak-version: 20.0.3 + +A https://devops-stack.io[DevOps Stack] module to deploy and configure https://www.keycloak.org/[Keycloak] as an OIDC provider. It is meant to be used mainly for testing purposes when deploying a local cluster using https://github.com/camptocamp/devops-stack-module-cluster-kind[KinD]. + +This repository contains 2 charts: + + - `keycloak-operator`: contains the CRDs and the other Kubernetes resources in order to install the operator as recommended for a vanilla Kubernetes installation in Keycloak's https://www.keycloak.org/operator/installation[documentation]; + - `keycloak`: installs Keycloak and associated resources (such as an ingress) as well as PostgreSQL deployment (optional if you provide the credentials for an external database). + +The Keycloak version installed is the *20.0.3*. https://www.keycloak.org/guides[Here] you will find the official guides of Keycloak, namely https://www.keycloak.org/guides#operator[the ones used for this module] in the _Operator_ section. + +Besides this module, we developed a xref:./oidc_bootstrap/README.adoc[submodule] to bootstrap a realm, groups and users so you can be up an running as fast as possible. + +== Usage + +This module can be declared by adding the following block on your Terraform configuration (below is an example when using KinD, but nothing stops you of using this module with the other cluster modules of the DevOps Stack): + +[source,terraform] +---- +module "keycloak" { + source = "git::https://github.com/camptocamp/devops-stack-module-keycloak.git?ref=" + + cluster_name = local.cluster_name + base_domain = format("%s.nip.io", replace(module.ingress.external_ip, ".", "-")) + cluster_issuer = local.cluster_issuer + argocd_namespace = module.argocd_bootstrap.argocd_namespace + + dependency_ids = { + traefik = module.ingress.id + cert-manager = module.cert-manager.id + } +} +---- + +=== Database Configuration + +IMPORTANT: We do not recommend using the PostgreSQL deployment in a production environment. It should be used only for development purposes and the persistence of the database is not guaranteed. + +In a production environment, it is recommended to use an external SQL database and to give this module only the connection details. In that case, the default PostgreSQL _sidecar_ will not be deployed. + +You can provide the credentials for connecting Keycloak to an external SQL database as follows: + +[source,terraform] +---- +module "keycloak" { + source = "git::https://github.com/camptocamp/devops-stack-module-keycloak.git?ref=" + + cluster_name = local.cluster_name + base_domain = format("%s.nip.io", replace(module.ingress.external_ip, ".", "-")) + cluster_issuer = local.cluster_issuer + argocd_namespace = module.argocd_bootstrap.argocd_namespace + + database = { + vendor = "mariadb | mssql | mysql | oracle | postgres" + host = "" + username = "" + password = "" + } + + dependency_ids = { + traefik = module.ingress.id + cert-manager = module.cert-manager.id + } +} +---- + +The https://www.keycloak.org/server/db[official documentation] provides more information about the supported database vendors. + +== Technical Reference + +=== Dependencies + +==== `module.argocd_bootstrap` + +This module needs a working Argo CD, so at least it depends on `module.argocd_bootstrap`. + +==== `module.ingress.id` + +Since there is an ingress deployed with this module, it needs to be deployed after Traefik so it depends on `module.ingress`. + +=== External Requirements + +==== `curl` + +IMPORTANT: Minimum required version is *7.71.0*. + +We were forced to use a `null_resource` in order to wait for a working Keycloak deployment. This resource runs a local command which uses `curl` to test if the Keycloak interface is up and running. Because of some flags that were only introduced on more recent versions of `curl`, you will need to have installed at least the version *7.71.0*. + // BEGIN_TF_DOCS === Requirements -No requirements. +The following requirements are needed by this module: + +- [[requirement_argocd]] <> (~> 4) + +- [[requirement_kubernetes]] <> (~> 2) + +- [[requirement_null]] <> (~> 3) + +- [[requirement_random]] <> (~> 3) + +- [[requirement_utils]] <> (~> 1) === Providers The following providers are used by this module: -- [[provider_argocd]] <> +- [[provider_argocd]] <> (~> 4) -- [[provider_null]] <> +- [[provider_kubernetes]] <> (~> 2) -- [[provider_random]] <> +- [[provider_null]] <> (~> 3) -- [[provider_utils]] <> +- [[provider_random]] <> (~> 3) + +- [[provider_utils]] <> (~> 1) === Modules @@ -26,38 +128,32 @@ The following resources are used by this module: - https://registry.terraform.io/providers/oboukili/argocd/latest/docs/resources/application[argocd_application.operator] (resource) - https://registry.terraform.io/providers/oboukili/argocd/latest/docs/resources/application[argocd_application.this] (resource) - https://registry.terraform.io/providers/oboukili/argocd/latest/docs/resources/project[argocd_project.this] (resource) -- https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource[null_resource.dependencies] (resource) -- https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource[null_resource.this] (resource) -- https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password[random_password.clientsecret] (resource) -- https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password[random_password.keycloak_passwords] (resource) +- https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.dependencies] (resource) +- https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.this] (resource) +- https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.wait_for_keycloak] (resource) +- https://registry.terraform.io/providers/random/latest/docs/resources/password[random_password.db_password] (resource) +- https://registry.terraform.io/providers/kubernetes/latest/docs/data-sources/secret[kubernetes_secret.admin_credentials] (data source) - https://registry.terraform.io/providers/cloudposse/utils/latest/docs/data-sources/deep_merge_yaml[utils_deep_merge_yaml.values] (data source) === Required Inputs The following input variables are required: -==== [[input_argocd]] <> +==== [[input_argocd_namespace]] <> -Description: n/a +Description: Namespace used by Argo CD where the Application and AppProject resources should be created. -Type: -[source,hcl] ----- -object({ - namespace = string - domain = string - }) ----- +Type: `string` ==== [[input_base_domain]] <> -Description: n/a +Description: Base domain of the cluster. Value used for the ingress' URL of the application. Type: `string` ==== [[input_cluster_name]] <> -Description: n/a +Description: Name given to the cluster. Value used for the ingress' URL of the application. Type: `string` @@ -65,41 +161,74 @@ Type: `string` The following input variables are optional (have default values): +==== [[input_app_autosync]] <> + +Description: Automated sync options for the Argo CD Application resource. + +Type: +[source,hcl] +---- +object({ + allow_empty = optional(bool) + prune = optional(bool) + self_heal = optional(bool) + }) +---- + +Default: +[source,json] +---- +{ + "allow_empty": false, + "prune": true, + "self_heal": true +} +---- + ==== [[input_cluster_issuer]] <> -Description: n/a +Description: SSL certificate issuer to use. Usually you would configure this value as `letsencrypt-staging` or `letsencrypt-prod` on your root `*.tf` files. Type: `string` Default: `"ca-issuer"` -==== [[input_dependency_ids]] <> +==== [[input_database]] <> -Description: n/a +Description: Keycloak external database server configuration. -Type: `map(string)` +Type: +[source,hcl] +---- +object({ + vendor = string + host = string + username = string + password = string + }) +---- -Default: `{}` +Default: `null` -==== [[input_extra_yaml]] <> +==== [[input_dependency_ids]] <> -Description: n/a +Description: IDs of the other modules on which this module depends on. -Type: `list(string)` +Type: `map(string)` -Default: `[]` +Default: `{}` -==== [[input_keycloak]] <> +==== [[input_helm_values]] <> -Description: Keycloak settings +Description: Helm chart value overrides. They should be passed as a list of HCL structures. Type: `any` -Default: `{}` +Default: `[]` ==== [[input_namespace]] <> -Description: n/a +Description: Namespace where the applications's Kubernetes resources should be created. Namespace will be created in case it doesn't exist. Type: `string` @@ -111,36 +240,49 @@ Description: Override of target revision of the application chart. Type: `string` -Default: `"v1.0.0"` +Default: `"v1.0.0-alpha.1"` === Outputs The following outputs are exported: -==== [[output_id]] <> +==== [[output_admin_credentials]] <> -Description: n/a +Description: Credentials for the administrator user of the master realm created on deployment. -==== [[output_keycloak_users]] <> +==== [[output_id]] <> -Description: n/a +Description: ID to pass other modules in order to refer to this module as a dependency. +// END_TF_DOCS -==== [[output_oidc]] <> +=== Reference in table format -Description: OIDC values -// END_TF_DOCS +.Show tables +[%collapsible] +==== // BEGIN_TF_TABLES += Requirements +[cols="a,a",options="header,autowidth"] +|=== +|Name |Version +|[[requirement_argocd]] <> |~> 4 +|[[requirement_kubernetes]] <> |~> 2 +|[[requirement_null]] <> |~> 3 +|[[requirement_random]] <> |~> 3 +|[[requirement_utils]] <> |~> 1 +|=== = Providers [cols="a,a",options="header,autowidth"] |=== |Name |Version -|[[provider_argocd]] <> |n/a -|[[provider_null]] <> |n/a -|[[provider_random]] <> |n/a -|[[provider_utils]] <> |n/a +|[[provider_argocd]] <> |~> 4 +|[[provider_kubernetes]] <> |~> 2 +|[[provider_null]] <> |~> 3 +|[[provider_random]] <> |~> 3 +|[[provider_utils]] <> |~> 1 |=== = Resources @@ -151,10 +293,11 @@ Description: OIDC values |https://registry.terraform.io/providers/oboukili/argocd/latest/docs/resources/application[argocd_application.operator] |resource |https://registry.terraform.io/providers/oboukili/argocd/latest/docs/resources/application[argocd_application.this] |resource |https://registry.terraform.io/providers/oboukili/argocd/latest/docs/resources/project[argocd_project.this] |resource -|https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource[null_resource.dependencies] |resource -|https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource[null_resource.this] |resource -|https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password[random_password.clientsecret] |resource -|https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password[random_password.keycloak_passwords] |resource +|https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.dependencies] |resource +|https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.this] |resource +|https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.wait_for_keycloak] |resource +|https://registry.terraform.io/providers/random/latest/docs/resources/password[random_password.db_password] |resource +|https://registry.terraform.io/providers/kubernetes/latest/docs/data-sources/secret[kubernetes_secret.admin_credentials] |data source |https://registry.terraform.io/providers/cloudposse/utils/latest/docs/data-sources/deep_merge_yaml[utils_deep_merge_yaml.values] |data source |=== @@ -163,59 +306,87 @@ Description: OIDC values [cols="a,a,a,a,a",options="header,autowidth"] |=== |Name |Description |Type |Default |Required -|[[input_argocd]] <> -|n/a +|[[input_app_autosync]] <> +|Automated sync options for the Argo CD Application resource. | [source] ---- object({ - namespace = string - domain = string + allow_empty = optional(bool) + prune = optional(bool) + self_heal = optional(bool) }) ---- +| + +[source] +---- +{ + "allow_empty": false, + "prune": true, + "self_heal": true +} +---- + +|no + +|[[input_argocd_namespace]] <> +|Namespace used by Argo CD where the Application and AppProject resources should be created. +|`string` |n/a |yes |[[input_base_domain]] <> -|n/a +|Base domain of the cluster. Value used for the ingress' URL of the application. |`string` |n/a |yes |[[input_cluster_issuer]] <> -|n/a +|SSL certificate issuer to use. Usually you would configure this value as `letsencrypt-staging` or `letsencrypt-prod` on your root `*.tf` files. |`string` |`"ca-issuer"` |no |[[input_cluster_name]] <> -|n/a +|Name given to the cluster. Value used for the ingress' URL of the application. |`string` |n/a |yes +|[[input_database]] <> +|Keycloak external database server configuration. +| + +[source] +---- +object({ + vendor = string + host = string + username = string + password = string + }) +---- + +|`null` +|no + |[[input_dependency_ids]] <> -|n/a +|IDs of the other modules on which this module depends on. |`map(string)` |`{}` |no -|[[input_extra_yaml]] <> -|n/a -|`list(string)` -|`[]` -|no - -|[[input_keycloak]] <> -|Keycloak settings +|[[input_helm_values]] <> +|Helm chart value overrides. They should be passed as a list of HCL structures. |`any` -|`{}` +|`[]` |no |[[input_namespace]] <> -|n/a +|Namespace where the applications's Kubernetes resources should be created. Namespace will be created in case it doesn't exist. |`string` |`"keycloak"` |no @@ -223,7 +394,7 @@ object({ |[[input_target_revision]] <> |Override of target revision of the application chart. |`string` -|`"v1.0.0"` +|`"v1.0.0-alpha.1"` |no |=== @@ -233,8 +404,8 @@ object({ [cols="a,a",options="header,autowidth"] |=== |Name |Description -|[[output_id]] <> |n/a -|[[output_keycloak_users]] <> |n/a -|[[output_oidc]] <> |OIDC values +|[[output_admin_credentials]] <> |Credentials for the administrator user of the master realm created on deployment. +|[[output_id]] <> |ID to pass other modules in order to refer to this module as a dependency. |=== -// END_TF_TABLES \ No newline at end of file +// END_TF_TABLES +==== diff --git a/README.md b/README.md deleted file mode 100644 index 23149d3..0000000 --- a/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# devops-stack-module-keycloak - -A [DevOps Stack](https://devops-stack.io) module to deploy and configure [Keycloak](https://www.keycloak.org/) as an OIDC provider. - - -## Usage - -```hcl -module "oidc" { - source = "git::https://github.com/camptocamp/devops-stack-module-keycloak.git/" - - cluster_name = var.cluster_name - argocd = { - namespace = module.cluster.argocd_namespace - domain = module.cluster.argocd_domain - } - base_domain = module.cluster.base_domain - cluster_issuer = "ca-issuer" - - depends_on = [ module.ingress ] -} -``` diff --git a/charts/keycloak-operator/Chart.yaml b/charts/keycloak-operator/Chart.yaml new file mode 100644 index 0000000..dcc8bf1 --- /dev/null +++ b/charts/keycloak-operator/Chart.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: "v2" +name: "keycloak-operator" +version: "0" +description: | + This chart installs the CRDs needed for Keycloak as well as the Keycloak operator. + We created this chart ever since the Keycloak developer team stopped publishing their chart. + This chart is not templatized at all and does not need a values.yaml. It simply groups together the resource + definitions for the Keycloak Operator, available in the following repository: + https://github.com/keycloak/keycloak-k8s-resources/ diff --git a/charts/keycloak-operator/crds/keycloakrealmimports.k8s.keycloak.org-v1.yml b/charts/keycloak-operator/crds/keycloakrealmimports.k8s.keycloak.org-v1.yml new file mode 100644 index 0000000..b617a30 --- /dev/null +++ b/charts/keycloak-operator/crds/keycloakrealmimports.k8s.keycloak.org-v1.yml @@ -0,0 +1,2248 @@ +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: keycloakrealmimports.k8s.keycloak.org +spec: + group: k8s.keycloak.org + names: + kind: KeycloakRealmImport + plural: keycloakrealmimports + singular: keycloakrealmimport + scope: Namespaced + versions: + - name: v2alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + keycloakCRName: + description: "The name of the Keycloak CR to reference, in the same\ + \ namespace." + type: string + realm: + description: The RealmRepresentation to import into Keycloak. + properties: + webAuthnPolicyAvoidSameAuthenticatorRegister: + type: boolean + federatedUsers: + items: + properties: + id: + type: string + clientConsents: + items: + properties: + grantedClientScopes: + items: + type: string + type: array + grantedRealmRoles: + items: + type: string + type: array + lastUpdatedDate: + type: integer + createdDate: + type: integer + clientId: + type: string + type: object + type: array + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + requiredActions: + items: + type: string + type: array + enabled: + type: boolean + realmRoles: + items: + type: string + type: array + createdTimestamp: + type: integer + emailVerified: + type: boolean + disableableCredentialTypes: + items: + type: string + type: array + socialLinks: + items: + properties: + socialUserId: + type: string + socialProvider: + type: string + socialUsername: + type: string + type: object + type: array + username: + type: string + federationLink: + type: string + access: + additionalProperties: + type: boolean + type: object + totp: + type: boolean + serviceAccountClientId: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + federatedIdentities: + items: + properties: + userId: + type: string + identityProvider: + type: string + userName: + type: string + type: object + type: array + firstName: + type: string + self: + type: string + notBefore: + type: integer + groups: + items: + type: string + type: array + credentials: + items: + properties: + id: + type: string + period: + type: integer + counter: + type: integer + value: + type: string + hashIterations: + type: integer + algorithm: + type: string + hashedSaltedValue: + type: string + type: + type: string + priority: + type: integer + device: + type: string + temporary: + type: boolean + userLabel: + type: string + createdDate: + type: integer + secretData: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + credentialData: + type: string + salt: + type: string + digits: + type: integer + type: object + type: array + applicationRoles: + additionalProperties: + items: + type: string + type: array + type: object + lastName: + type: string + email: + type: string + origin: + type: string + type: object + type: array + adminEventsEnabled: + type: boolean + registrationEmailAsUsername: + type: boolean + keycloakVersion: + type: string + oauth2DeviceCodeLifespan: + type: integer + sslRequired: + type: string + realm: + type: string + defaultGroups: + items: + type: string + type: array + enabled: + type: boolean + webAuthnPolicySignatureAlgorithms: + items: + type: string + type: array + ssoSessionMaxLifespanRememberMe: + type: integer + webAuthnPolicyRpId: + type: string + webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister: + type: boolean + users: + items: + properties: + id: + type: string + clientConsents: + items: + properties: + grantedClientScopes: + items: + type: string + type: array + grantedRealmRoles: + items: + type: string + type: array + lastUpdatedDate: + type: integer + createdDate: + type: integer + clientId: + type: string + type: object + type: array + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + requiredActions: + items: + type: string + type: array + enabled: + type: boolean + realmRoles: + items: + type: string + type: array + createdTimestamp: + type: integer + emailVerified: + type: boolean + disableableCredentialTypes: + items: + type: string + type: array + socialLinks: + items: + properties: + socialUserId: + type: string + socialProvider: + type: string + socialUsername: + type: string + type: object + type: array + username: + type: string + federationLink: + type: string + access: + additionalProperties: + type: boolean + type: object + totp: + type: boolean + serviceAccountClientId: + type: string + attributes: + additionalProperties: + items: + type: string + type: array + type: object + federatedIdentities: + items: + properties: + userId: + type: string + identityProvider: + type: string + userName: + type: string + type: object + type: array + firstName: + type: string + self: + type: string + notBefore: + type: integer + groups: + items: + type: string + type: array + credentials: + items: + properties: + id: + type: string + period: + type: integer + counter: + type: integer + value: + type: string + hashIterations: + type: integer + algorithm: + type: string + hashedSaltedValue: + type: string + type: + type: string + priority: + type: integer + device: + type: string + temporary: + type: boolean + userLabel: + type: string + createdDate: + type: integer + secretData: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + credentialData: + type: string + salt: + type: string + digits: + type: integer + type: object + type: array + applicationRoles: + additionalProperties: + items: + type: string + type: array + type: object + lastName: + type: string + email: + type: string + origin: + type: string + type: object + type: array + clientTemplates: + items: + properties: + protocol: + type: string + id: + type: string + fullScopeAllowed: + type: boolean + frontchannelLogout: + type: boolean + serviceAccountsEnabled: + type: boolean + standardFlowEnabled: + type: boolean + description: + type: string + publicClient: + type: boolean + consentRequired: + type: boolean + bearerOnly: + type: boolean + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + name: + type: string + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + attributes: + additionalProperties: + type: string + type: object + type: object + type: array + webAuthnPolicyPasswordlessUserVerificationRequirement: + type: string + registrationFlow: + type: string + publicKey: + type: string + webAuthnPolicyPasswordlessCreateTimeout: + type: integer + authenticationFlows: + items: + properties: + id: + type: string + providerId: + type: string + authenticationExecutions: + items: + properties: + userSetupAllowed: + type: boolean + flowAlias: + type: string + autheticatorFlow: + type: boolean + authenticatorConfig: + type: string + authenticator: + type: string + priority: + type: integer + requirement: + type: string + authenticatorFlow: + type: boolean + type: object + type: array + topLevel: + type: boolean + alias: + type: string + builtIn: + type: boolean + description: + type: string + type: object + type: array + applicationScopeMappings: + additionalProperties: + items: + properties: + clientTemplate: + type: string + self: + type: string + clientScope: + type: string + client: + type: string + roles: + items: + type: string + type: array + type: object + type: array + type: object + offlineSessionMaxLifespan: + type: integer + codeSecret: + type: string + offlineSessionIdleTimeout: + type: integer + quickLoginCheckMilliSeconds: + type: integer + privateKey: + type: string + webAuthnPolicyRpEntityName: + type: string + emailTheme: + type: string + accessCodeLifespanLogin: + type: integer + passwordPolicy: + type: string + ssoSessionIdleTimeoutRememberMe: + type: integer + resetPasswordAllowed: + type: boolean + failureFactor: + type: integer + otpPolicyAlgorithm: + type: string + requiredActions: + items: + properties: + providerId: + type: string + alias: + type: string + defaultAction: + type: boolean + priority: + type: integer + name: + type: string + enabled: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + actionTokenGeneratedByUserLifespan: + type: integer + clientAuthenticationFlow: + type: string + webAuthnPolicyAuthenticatorAttachment: + type: string + actionTokenGeneratedByAdminLifespan: + type: integer + id: + type: string + clientPolicies: + type: object + x-kubernetes-preserve-unknown-fields: true + webAuthnPolicyUserVerificationRequirement: + type: string + loginTheme: + type: string + requiredCredentials: + items: + type: string + type: array + webAuthnPolicyPasswordlessAttestationConveyancePreference: + type: string + directGrantFlow: + type: string + identityProviderMappers: + items: + properties: + id: + type: string + name: + type: string + identityProviderMapper: + type: string + identityProviderAlias: + type: string + config: + additionalProperties: + type: string + type: object + type: object + type: array + dockerAuthenticationFlow: + type: string + browserFlow: + type: string + bruteForceProtected: + type: boolean + displayNameHtml: + type: string + ssoSessionIdleTimeout: + type: integer + browserSecurityHeaders: + additionalProperties: + type: string + type: object + eventsListeners: + items: + type: string + type: array + accessTokenLifespan: + type: integer + applications: + items: + properties: + name: + type: string + claims: + properties: + picture: + type: boolean + gender: + type: boolean + phone: + type: boolean + website: + type: boolean + email: + type: boolean + profile: + type: boolean + address: + type: boolean + name: + type: boolean + username: + type: boolean + locale: + type: boolean + type: object + id: + type: string + frontchannelLogout: + type: boolean + useTemplateConfig: + type: boolean + registrationAccessToken: + type: string + baseUrl: + type: string + serviceAccountsEnabled: + type: boolean + registeredNodes: + additionalProperties: + type: integer + type: object + useTemplateMappers: + type: boolean + description: + type: string + publicClient: + type: boolean + useTemplateScope: + type: boolean + authorizationSettings: + properties: + id: + type: string + resources: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + name: + type: string + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + id: + type: string + owner: + type: string + resources: + items: + type: string + type: array + policies: + items: + type: string + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + resourcesData: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + name: + type: string + type: + type: string + scopesData: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + description: + type: string + scopes: + items: + type: string + type: array + type: object + type: array + clientId: + type: string + allowRemoteResourceManagement: + type: boolean + type: object + clientId: + type: string + enabled: + type: boolean + clientAuthenticatorType: + type: string + surrogateAuthRequired: + type: boolean + webOrigins: + items: + type: string + type: array + authorizationServicesEnabled: + type: boolean + secret: + type: string + protocol: + type: string + fullScopeAllowed: + type: boolean + nodeReRegistrationTimeout: + type: integer + clientTemplate: + type: string + access: + additionalProperties: + type: boolean + type: object + alwaysDisplayInConsole: + type: boolean + rootUrl: + type: string + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + standardFlowEnabled: + type: boolean + optionalClientScopes: + items: + type: string + type: array + consentRequired: + type: boolean + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + bearerOnly: + type: boolean + defaultClientScopes: + items: + type: string + type: array + adminUrl: + type: string + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + notBefore: + type: integer + directGrantsOnly: + type: boolean + defaultRoles: + items: + type: string + type: array + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + origin: + type: string + attributes: + additionalProperties: + type: string + type: object + redirectUris: + items: + type: string + type: array + type: object + type: array + otpPolicyCodeReusable: + type: boolean + clientProfiles: + type: object + x-kubernetes-preserve-unknown-fields: true + userFederationMappers: + items: + properties: + id: + type: string + federationProviderDisplayName: + type: string + federationMapperType: + type: string + name: + type: string + config: + additionalProperties: + type: string + type: object + type: object + type: array + enabledEventTypes: + items: + type: string + type: array + otpPolicyLookAheadWindow: + type: integer + displayName: + type: string + eventsEnabled: + type: boolean + clientSessionMaxLifespan: + type: integer + roles: + properties: + application: + additionalProperties: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: + properties: + realm: + items: + type: string + type: array + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + type: object + containerId: + type: string + composite: + type: boolean + type: object + type: array + type: object + client: + additionalProperties: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: + properties: + realm: + items: + type: string + type: array + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + type: object + containerId: + type: string + composite: + type: boolean + type: object + type: array + type: object + realm: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: + properties: + realm: + items: + type: string + type: array + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + type: object + containerId: + type: string + composite: + type: boolean + type: object + type: array + type: object + groups: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + access: + additionalProperties: + type: boolean + type: object + realmRoles: + items: + type: string + type: array + path: + type: string + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + name: + type: string + subGroups: + items: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + access: + additionalProperties: + type: boolean + type: object + realmRoles: + items: + type: string + type: array + path: + type: string + clientRoles: + additionalProperties: + items: + type: string + type: array + type: object + name: + type: string + type: object + type: array + type: object + type: array + webAuthnPolicyCreateTimeout: + type: integer + webAuthnPolicyAttestationConveyancePreference: + type: string + clientOfflineSessionIdleTimeout: + type: integer + notBefore: + type: integer + webAuthnPolicyPasswordlessRpEntityName: + type: string + verifyEmail: + type: boolean + clientScopeMappings: + additionalProperties: + items: + properties: + clientTemplate: + type: string + self: + type: string + clientScope: + type: string + client: + type: string + roles: + items: + type: string + type: array + type: object + type: array + type: object + identityProviders: + items: + properties: + storeToken: + type: boolean + trustEmail: + type: boolean + updateProfileFirstLoginMode: + type: string + authenticateByDefault: + type: boolean + displayName: + type: string + providerId: + type: string + linkOnly: + type: boolean + postBrokerLoginFlowAlias: + type: string + alias: + type: string + enabled: + type: boolean + firstBrokerLoginFlowAlias: + type: string + internalId: + type: string + addReadTokenRoleOnCreate: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + resetCredentialsFlow: + type: string + duplicateEmailsAllowed: + type: boolean + maxDeltaTimeSeconds: + type: integer + offlineSessionMaxLifespanEnabled: + type: boolean + realmCacheEnabled: + type: boolean + attributes: + additionalProperties: + type: string + type: object + adminTheme: + type: string + loginWithEmailAllowed: + type: boolean + otpSupportedApplications: + items: + type: string + type: array + clientOfflineSessionMaxLifespan: + type: integer + userFederationProviders: + items: + properties: + id: + type: string + providerName: + type: string + displayName: + type: string + priority: + type: integer + fullSyncPeriod: + type: integer + lastSync: + type: integer + changedSyncPeriod: + type: integer + config: + additionalProperties: + type: string + type: object + type: object + type: array + internationalizationEnabled: + type: boolean + permanentLockout: + type: boolean + userManagedAccessAllowed: + type: boolean + smtpServer: + additionalProperties: + type: string + type: object + otpPolicyDigits: + type: integer + webAuthnPolicyPasswordlessSignatureAlgorithms: + items: + type: string + type: array + socialProviders: + additionalProperties: + type: string + type: object + otpPolicyInitialCounter: + type: integer + defaultSignatureAlgorithm: + type: string + refreshTokenMaxReuse: + type: integer + revokeRefreshToken: + type: boolean + accountTheme: + type: string + webAuthnPolicyPasswordlessAcceptableAaguids: + items: + type: string + type: array + webAuthnPolicyPasswordlessAuthenticatorAttachment: + type: string + supportedLocales: + items: + type: string + type: array + defaultDefaultClientScopes: + items: + type: string + type: array + authenticatorConfig: + items: + properties: + id: + type: string + alias: + type: string + config: + additionalProperties: + type: string + type: object + type: object + type: array + webAuthnPolicyPasswordlessRpId: + type: string + scopeMappings: + items: + properties: + clientTemplate: + type: string + self: + type: string + clientScope: + type: string + client: + type: string + roles: + items: + type: string + type: array + type: object + type: array + clientScopes: + items: + properties: + protocol: + type: string + id: + type: string + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + name: + type: string + description: + type: string + attributes: + additionalProperties: + type: string + type: object + type: object + type: array + oauth2DevicePollingInterval: + type: integer + eventsExpiration: + type: integer + certificate: + type: string + defaultRole: + properties: + attributes: + additionalProperties: + items: + type: string + type: array + type: object + id: + type: string + clientRole: + type: boolean + name: + type: string + description: + type: string + scopeParamRequired: + type: boolean + composites: + properties: + realm: + items: + type: string + type: array + application: + additionalProperties: + items: + type: string + type: array + type: object + client: + additionalProperties: + items: + type: string + type: array + type: object + type: object + containerId: + type: string + composite: + type: boolean + type: object + defaultOptionalClientScopes: + items: + type: string + type: array + editUsernameAllowed: + type: boolean + defaultLocale: + type: string + webAuthnPolicyRequireResidentKey: + type: string + oauthClients: + items: + properties: + name: + type: string + claims: + properties: + picture: + type: boolean + gender: + type: boolean + phone: + type: boolean + website: + type: boolean + email: + type: boolean + profile: + type: boolean + address: + type: boolean + name: + type: boolean + username: + type: boolean + locale: + type: boolean + type: object + id: + type: string + frontchannelLogout: + type: boolean + useTemplateConfig: + type: boolean + registrationAccessToken: + type: string + baseUrl: + type: string + serviceAccountsEnabled: + type: boolean + registeredNodes: + additionalProperties: + type: integer + type: object + useTemplateMappers: + type: boolean + description: + type: string + publicClient: + type: boolean + useTemplateScope: + type: boolean + authorizationSettings: + properties: + id: + type: string + resources: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + name: + type: string + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + id: + type: string + owner: + type: string + resources: + items: + type: string + type: array + policies: + items: + type: string + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + resourcesData: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + name: + type: string + type: + type: string + scopesData: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + description: + type: string + scopes: + items: + type: string + type: array + type: object + type: array + clientId: + type: string + allowRemoteResourceManagement: + type: boolean + type: object + clientId: + type: string + enabled: + type: boolean + clientAuthenticatorType: + type: string + surrogateAuthRequired: + type: boolean + webOrigins: + items: + type: string + type: array + authorizationServicesEnabled: + type: boolean + secret: + type: string + protocol: + type: string + fullScopeAllowed: + type: boolean + nodeReRegistrationTimeout: + type: integer + clientTemplate: + type: string + access: + additionalProperties: + type: boolean + type: object + alwaysDisplayInConsole: + type: boolean + rootUrl: + type: string + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + standardFlowEnabled: + type: boolean + optionalClientScopes: + items: + type: string + type: array + consentRequired: + type: boolean + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + bearerOnly: + type: boolean + defaultClientScopes: + items: + type: string + type: array + adminUrl: + type: string + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + notBefore: + type: integer + directGrantsOnly: + type: boolean + defaultRoles: + items: + type: string + type: array + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + origin: + type: string + attributes: + additionalProperties: + type: string + type: object + redirectUris: + items: + type: string + type: array + type: object + type: array + adminEventsDetailsEnabled: + type: boolean + ssoSessionMaxLifespan: + type: integer + accessCodeLifespanUserAction: + type: integer + registrationAllowed: + type: boolean + social: + type: boolean + accessTokenLifespanForImplicitFlow: + type: integer + rememberMe: + type: boolean + maxFailureWaitSeconds: + type: integer + defaultRoles: + items: + type: string + type: array + otpPolicyType: + type: string + otpPolicyPeriod: + type: integer + accessCodeLifespan: + type: integer + minimumQuickLoginWaitSeconds: + type: integer + webAuthnPolicyAcceptableAaguids: + items: + type: string + type: array + updateProfileOnInitialSocialLogin: + type: boolean + clientSessionIdleTimeout: + type: integer + webAuthnPolicyPasswordlessRequireResidentKey: + type: string + waitIncrementSeconds: + type: integer + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + clients: + items: + properties: + id: + type: string + frontchannelLogout: + type: boolean + useTemplateConfig: + type: boolean + registrationAccessToken: + type: string + baseUrl: + type: string + serviceAccountsEnabled: + type: boolean + registeredNodes: + additionalProperties: + type: integer + type: object + useTemplateMappers: + type: boolean + description: + type: string + publicClient: + type: boolean + useTemplateScope: + type: boolean + authorizationSettings: + properties: + id: + type: string + resources: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + name: + type: string + policyEnforcementMode: + enum: + - stableIndex + - PERMISSIVE + - ENFORCING + - DISABLED + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + policies: + items: + properties: + config: + additionalProperties: + type: string + type: object + id: + type: string + owner: + type: string + resources: + items: + type: string + type: array + policies: + items: + type: string + type: array + decisionStrategy: + enum: + - AFFIRMATIVE + - stableIndex + - CONSENSUS + - UNANIMOUS + type: string + logic: + enum: + - stableIndex + - POSITIVE + - NEGATIVE + type: string + resourcesData: + items: + properties: + _id: + type: string + uris: + items: + type: string + type: array + attributes: + additionalProperties: + items: + type: string + type: array + type: object + displayName: + type: string + scopes: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + owner: + properties: + id: + type: string + name: + type: string + type: object + name: + type: string + type: + type: string + icon_uri: + type: string + ownerManagedAccess: + type: boolean + type: object + type: array + name: + type: string + type: + type: string + scopesData: + items: + properties: + id: + type: string + displayName: + type: string + name: + type: string + iconUri: + type: string + type: object + type: array + description: + type: string + scopes: + items: + type: string + type: array + type: object + type: array + clientId: + type: string + allowRemoteResourceManagement: + type: boolean + type: object + clientId: + type: string + enabled: + type: boolean + clientAuthenticatorType: + type: string + name: + type: string + surrogateAuthRequired: + type: boolean + webOrigins: + items: + type: string + type: array + authorizationServicesEnabled: + type: boolean + secret: + type: string + protocol: + type: string + fullScopeAllowed: + type: boolean + nodeReRegistrationTimeout: + type: integer + clientTemplate: + type: string + access: + additionalProperties: + type: boolean + type: object + alwaysDisplayInConsole: + type: boolean + rootUrl: + type: string + oauth2DeviceAuthorizationGrantEnabled: + type: boolean + standardFlowEnabled: + type: boolean + optionalClientScopes: + items: + type: string + type: array + consentRequired: + type: boolean + authenticationFlowBindingOverrides: + additionalProperties: + type: string + type: object + bearerOnly: + type: boolean + defaultClientScopes: + items: + type: string + type: array + adminUrl: + type: string + protocolMappers: + items: + properties: + protocol: + type: string + id: + type: string + name: + type: string + protocolMapper: + type: string + consentText: + type: string + consentRequired: + type: boolean + config: + additionalProperties: + type: string + type: object + type: object + type: array + notBefore: + type: integer + directGrantsOnly: + type: boolean + defaultRoles: + items: + type: string + type: array + directAccessGrantsEnabled: + type: boolean + implicitFlowEnabled: + type: boolean + origin: + type: string + attributes: + additionalProperties: + type: string + type: object + redirectUris: + items: + type: string + type: array + type: object + type: array + components: + additionalProperties: + items: + properties: + id: + type: string + providerId: + type: string + subType: + type: string + subComponents: + additionalProperties: + items: + properties: + id: + type: string + providerId: + type: string + subType: + type: string + name: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + type: object + type: array + type: object + name: + type: string + config: + additionalProperties: + items: + type: string + type: array + type: object + type: object + type: array + type: object + passwordCredentialGrantAllowed: + type: boolean + userCacheEnabled: + type: boolean + type: object + required: + - keycloakCRName + - realm + type: object + status: + properties: + conditions: + items: + properties: + status: + type: boolean + type: + type: string + message: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/keycloak-operator/crds/keycloaks.k8s.keycloak.org-v1.yml b/charts/keycloak-operator/crds/keycloaks.k8s.keycloak.org-v1.yml new file mode 100644 index 0000000..5003659 --- /dev/null +++ b/charts/keycloak-operator/crds/keycloaks.k8s.keycloak.org-v1.yml @@ -0,0 +1,2917 @@ +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: keycloaks.k8s.keycloak.org +spec: + group: k8s.keycloak.org + names: + kind: Keycloak + plural: keycloaks + shortNames: + - kc + singular: keycloak + scope: Namespaced + versions: + - name: v2alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + instances: + description: Number of Keycloak instances in HA mode. Default is 1. + type: integer + transaction: + description: In this section you can find all properties related to + the settings of transaction behavior. + properties: + xaEnabled: + description: Determine whether Keycloak should use a non-XA datasource + in case the database does not support XA transactions. + type: boolean + type: object + http: + description: In this section you can configure Keycloak features related + to HTTP and HTTPS + properties: + httpPort: + description: The used HTTP port. + type: integer + tlsSecret: + description: "A secret containing the TLS configuration for HTTPS.\ + \ Reference: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets." + type: string + httpsPort: + description: The used HTTPS port. + type: integer + httpEnabled: + description: Enables the HTTP listener. + type: boolean + type: object + hostname: + description: In this section you can configure Keycloak hostname and + related properties. + properties: + hostname: + description: Hostname for the Keycloak server. + type: string + strict: + description: Disables dynamically resolving the hostname from + request headers. + type: boolean + strictBackchannel: + description: By default backchannel URLs are dynamically resolved + from request headers to allow internal and external applications. + type: boolean + admin: + description: The hostname for accessing the administration console. + type: string + adminUrl: + description: "Set the base URL for accessing the administration\ + \ console, including scheme, host, port and path" + type: string + type: object + unsupported: + description: |- + In this section you can configure podTemplate advanced features, not production-ready, and not supported settings. + Use at your own risk and open an issue with your use-case if you don't find an alternative way. + properties: + podTemplate: + description: |- + You can configure that will be merged with the one configured by default by the operator. + Use at your own risk, we reserve the possibility to remove/change the way any field gets merged in future releases without notice. + Reference: https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + properties: + metadata: + properties: + generateName: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + clusterName: + type: string + resourceVersion: + type: string + annotations: + additionalProperties: + type: string + type: object + selfLink: + type: string + creationTimestamp: + type: string + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + ownerReferences: + items: + properties: + blockOwnerDeletion: + type: boolean + uid: + type: string + apiVersion: + type: string + name: + type: string + kind: + type: string + controller: + type: boolean + type: object + type: array + uid: + type: string + generation: + type: integer + name: + type: string + managedFields: + items: + properties: + time: + type: string + apiVersion: + type: string + fieldsV1: + type: object + fieldsType: + type: string + manager: + type: string + operation: + type: string + subresource: + type: string + type: object + type: array + namespace: + type: string + type: object + spec: + properties: + volumes: + items: + properties: + hostPath: + properties: + path: + type: string + type: + type: string + type: object + flexVolume: + properties: + readOnly: + type: boolean + options: + additionalProperties: + type: string + type: object + secretRef: + properties: + name: + type: string + type: object + fsType: + type: string + driver: + type: string + type: object + gcePersistentDisk: + properties: + readOnly: + type: boolean + pdName: + type: string + partition: + type: integer + fsType: + type: string + type: object + ephemeral: + properties: + volumeClaimTemplate: + properties: + metadata: + properties: + generateName: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + clusterName: + type: string + resourceVersion: + type: string + annotations: + additionalProperties: + type: string + type: object + selfLink: + type: string + creationTimestamp: + type: string + finalizers: + items: + type: string + type: array + labels: + additionalProperties: + type: string + type: object + ownerReferences: + items: + properties: + blockOwnerDeletion: + type: boolean + uid: + type: string + apiVersion: + type: string + name: + type: string + kind: + type: string + controller: + type: boolean + type: object + type: array + uid: + type: string + generation: + type: integer + name: + type: string + managedFields: + items: + properties: + time: + type: string + apiVersion: + type: string + fieldsV1: + type: object + fieldsType: + type: string + manager: + type: string + operation: + type: string + subresource: + type: string + type: object + type: array + namespace: + type: string + type: object + spec: + properties: + selector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + storageClassName: + type: string + dataSource: + properties: + name: + type: string + kind: + type: string + apiGroup: + type: string + type: object + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + dataSourceRef: + properties: + name: + type: string + kind: + type: string + apiGroup: + type: string + type: object + accessModes: + items: + type: string + type: array + volumeMode: + type: string + volumeName: + type: string + type: object + type: object + type: object + scaleIO: + properties: + readOnly: + type: boolean + storageMode: + type: string + storagePool: + type: string + system: + type: string + gateway: + type: string + secretRef: + properties: + name: + type: string + type: object + fsType: + type: string + sslEnabled: + type: boolean + volumeName: + type: string + protectionDomain: + type: string + type: object + csi: + properties: + nodePublishSecretRef: + properties: + name: + type: string + type: object + readOnly: + type: boolean + volumeAttributes: + additionalProperties: + type: string + type: object + fsType: + type: string + driver: + type: string + type: object + secret: + properties: + optional: + type: boolean + secretName: + type: string + items: + items: + properties: + path: + type: string + key: + type: string + mode: + type: integer + type: object + type: array + defaultMode: + type: integer + type: object + name: + type: string + vsphereVolume: + properties: + storagePolicyName: + type: string + storagePolicyID: + type: string + volumePath: + type: string + fsType: + type: string + type: object + gitRepo: + properties: + revision: + type: string + repository: + type: string + directory: + type: string + type: object + glusterfs: + properties: + path: + type: string + readOnly: + type: boolean + endpoints: + type: string + type: object + nfs: + properties: + path: + type: string + readOnly: + type: boolean + server: + type: string + type: object + cinder: + properties: + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + fsType: + type: string + volumeID: + type: string + type: object + flocker: + properties: + datasetUUID: + type: string + datasetName: + type: string + type: object + quobyte: + properties: + group: + type: string + readOnly: + type: boolean + volume: + type: string + user: + type: string + registry: + type: string + tenant: + type: string + type: object + photonPersistentDisk: + properties: + pdID: + type: string + fsType: + type: string + type: object + persistentVolumeClaim: + properties: + readOnly: + type: boolean + claimName: + type: string + type: object + awsElasticBlockStore: + properties: + readOnly: + type: boolean + partition: + type: integer + fsType: + type: string + volumeID: + type: string + type: object + configMap: + properties: + optional: + type: boolean + items: + items: + properties: + path: + type: string + key: + type: string + mode: + type: integer + type: object + type: array + defaultMode: + type: integer + name: + type: string + type: object + storageos: + properties: + readOnly: + type: boolean + volumeNamespace: + type: string + secretRef: + properties: + name: + type: string + type: object + fsType: + type: string + volumeName: + type: string + type: object + portworxVolume: + properties: + readOnly: + type: boolean + fsType: + type: string + volumeID: + type: string + type: object + iscsi: + properties: + readOnly: + type: boolean + chapAuthSession: + type: boolean + lun: + type: integer + targetPortal: + type: string + iscsiInterface: + type: string + portals: + items: + type: string + type: array + initiatorName: + type: string + secretRef: + properties: + name: + type: string + type: object + fsType: + type: string + iqn: + type: string + chapAuthDiscovery: + type: boolean + type: object + rbd: + properties: + readOnly: + type: boolean + pool: + type: string + keyring: + type: string + image: + type: string + secretRef: + properties: + name: + type: string + type: object + monitors: + items: + type: string + type: array + fsType: + type: string + user: + type: string + type: object + azureFile: + properties: + readOnly: + type: boolean + secretName: + type: string + shareName: + type: string + type: object + downwardAPI: + properties: + items: + items: + properties: + path: + type: string + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + mode: + type: integer + type: object + type: array + defaultMode: + type: integer + type: object + projected: + properties: + defaultMode: + type: integer + sources: + items: + properties: + secret: + properties: + optional: + type: boolean + items: + items: + properties: + path: + type: string + key: + type: string + mode: + type: integer + type: object + type: array + name: + type: string + type: object + configMap: + properties: + optional: + type: boolean + items: + items: + properties: + path: + type: string + key: + type: string + mode: + type: integer + type: object + type: array + name: + type: string + type: object + serviceAccountToken: + properties: + path: + type: string + audience: + type: string + expirationSeconds: + type: integer + type: object + downwardAPI: + properties: + items: + items: + properties: + path: + type: string + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + mode: + type: integer + type: object + type: array + type: object + type: object + type: array + type: object + azureDisk: + properties: + readOnly: + type: boolean + diskName: + type: string + cachingMode: + type: string + fsType: + type: string + kind: + type: string + diskURI: + type: string + type: object + cephfs: + properties: + path: + type: string + readOnly: + type: boolean + secretRef: + properties: + name: + type: string + type: object + monitors: + items: + type: string + type: array + secretFile: + type: string + user: + type: string + type: object + emptyDir: + properties: + sizeLimit: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + medium: + type: string + type: object + fc: + properties: + readOnly: + type: boolean + lun: + type: integer + wwids: + items: + type: string + type: array + targetWWNs: + items: + type: string + type: array + fsType: + type: string + type: object + type: object + type: array + restartPolicy: + type: string + terminationGracePeriodSeconds: + type: integer + setHostnameAsFQDN: + type: boolean + dnsConfig: + properties: + nameservers: + items: + type: string + type: array + searches: + items: + type: string + type: array + options: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + type: object + securityContext: + properties: + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + windowsOptions: + properties: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: + type: string + runAsUserName: + type: string + type: object + sysctls: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + fsGroupChangePolicy: + type: string + seLinuxOptions: + properties: + role: + type: string + type: + type: string + user: + type: string + level: + type: string + type: object + fsGroup: + type: integer + supplementalGroups: + items: + type: integer + type: array + runAsUser: + type: integer + seccompProfile: + properties: + type: + type: string + localhostProfile: + type: string + type: object + type: object + imagePullSecrets: + items: + properties: + name: + type: string + type: object + type: array + subdomain: + type: string + serviceAccount: + type: string + activeDeadlineSeconds: + type: integer + priority: + type: integer + ephemeralContainers: + items: + properties: + lifecycle: + properties: + postStart: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + preStop: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + command: + items: + type: string + type: array + livenessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdin: + type: boolean + image: + type: string + targetContainerName: + type: string + terminationMessagePolicy: + type: string + readinessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + terminationMessagePath: + type: string + env: + items: + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + secretKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + type: object + name: + type: string + type: object + type: array + tty: + type: boolean + args: + items: + type: string + type: array + startupProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdinOnce: + type: boolean + ports: + items: + properties: + containerPort: + type: integer + hostPort: + type: integer + name: + type: string + protocol: + type: string + hostIP: + type: string + type: object + type: array + workingDir: + type: string + envFrom: + items: + properties: + prefix: + type: string + configMapRef: + properties: + optional: + type: boolean + name: + type: string + type: object + secretRef: + properties: + optional: + type: boolean + name: + type: string + type: object + type: object + type: array + volumeMounts: + items: + properties: + readOnly: + type: boolean + subPathExpr: + type: string + mountPath: + type: string + mountPropagation: + type: string + subPath: + type: string + name: + type: string + type: object + type: array + securityContext: + properties: + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + windowsOptions: + properties: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: + type: string + runAsUserName: + type: string + type: object + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + seLinuxOptions: + properties: + role: + type: string + type: + type: string + user: + type: string + level: + type: string + type: object + readOnlyRootFilesystem: + type: boolean + privileged: + type: boolean + runAsUser: + type: integer + procMount: + type: string + seccompProfile: + properties: + type: + type: string + localhostProfile: + type: string + type: object + type: object + name: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + imagePullPolicy: + type: string + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + type: object + type: array + type: object + type: array + automountServiceAccountToken: + type: boolean + containers: + items: + properties: + lifecycle: + properties: + postStart: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + preStop: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + command: + items: + type: string + type: array + livenessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdin: + type: boolean + image: + type: string + terminationMessagePolicy: + type: string + readinessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + terminationMessagePath: + type: string + env: + items: + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + secretKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + type: object + name: + type: string + type: object + type: array + tty: + type: boolean + args: + items: + type: string + type: array + startupProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdinOnce: + type: boolean + ports: + items: + properties: + containerPort: + type: integer + hostPort: + type: integer + name: + type: string + protocol: + type: string + hostIP: + type: string + type: object + type: array + workingDir: + type: string + envFrom: + items: + properties: + prefix: + type: string + configMapRef: + properties: + optional: + type: boolean + name: + type: string + type: object + secretRef: + properties: + optional: + type: boolean + name: + type: string + type: object + type: object + type: array + volumeMounts: + items: + properties: + readOnly: + type: boolean + subPathExpr: + type: string + mountPath: + type: string + mountPropagation: + type: string + subPath: + type: string + name: + type: string + type: object + type: array + securityContext: + properties: + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + windowsOptions: + properties: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: + type: string + runAsUserName: + type: string + type: object + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + seLinuxOptions: + properties: + role: + type: string + type: + type: string + user: + type: string + level: + type: string + type: object + readOnlyRootFilesystem: + type: boolean + privileged: + type: boolean + runAsUser: + type: integer + procMount: + type: string + seccompProfile: + properties: + type: + type: string + localhostProfile: + type: string + type: object + type: object + name: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + imagePullPolicy: + type: string + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + type: object + type: array + type: object + type: array + initContainers: + items: + properties: + lifecycle: + properties: + postStart: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + preStop: + properties: + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + type: object + command: + items: + type: string + type: array + livenessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdin: + type: boolean + image: + type: string + terminationMessagePolicy: + type: string + readinessProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + terminationMessagePath: + type: string + env: + items: + properties: + value: + type: string + valueFrom: + properties: + configMapKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + fieldRef: + properties: + apiVersion: + type: string + fieldPath: + type: string + type: object + resourceFieldRef: + properties: + containerName: + type: string + divisor: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + resource: + type: string + type: object + secretKeyRef: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + type: object + name: + type: string + type: object + type: array + tty: + type: boolean + args: + items: + type: string + type: array + startupProbe: + properties: + periodSeconds: + type: integer + failureThreshold: + type: integer + initialDelaySeconds: + type: integer + grpc: + properties: + port: + type: integer + service: + type: string + type: object + successThreshold: + type: integer + terminationGracePeriodSeconds: + type: integer + tcpSocket: + properties: + host: + type: string + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + timeoutSeconds: + type: integer + exec: + properties: + command: + items: + type: string + type: array + type: object + httpGet: + properties: + path: + type: string + scheme: + type: string + host: + type: string + httpHeaders: + items: + properties: + value: + type: string + name: + type: string + type: object + type: array + port: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + stdinOnce: + type: boolean + ports: + items: + properties: + containerPort: + type: integer + hostPort: + type: integer + name: + type: string + protocol: + type: string + hostIP: + type: string + type: object + type: array + workingDir: + type: string + envFrom: + items: + properties: + prefix: + type: string + configMapRef: + properties: + optional: + type: boolean + name: + type: string + type: object + secretRef: + properties: + optional: + type: boolean + name: + type: string + type: object + type: object + type: array + volumeMounts: + items: + properties: + readOnly: + type: boolean + subPathExpr: + type: string + mountPath: + type: string + mountPropagation: + type: string + subPath: + type: string + name: + type: string + type: object + type: array + securityContext: + properties: + runAsGroup: + type: integer + runAsNonRoot: + type: boolean + windowsOptions: + properties: + gmsaCredentialSpecName: + type: string + hostProcess: + type: boolean + gmsaCredentialSpec: + type: string + runAsUserName: + type: string + type: object + allowPrivilegeEscalation: + type: boolean + capabilities: + properties: + add: + items: + type: string + type: array + drop: + items: + type: string + type: array + type: object + seLinuxOptions: + properties: + role: + type: string + type: + type: string + user: + type: string + level: + type: string + type: object + readOnlyRootFilesystem: + type: boolean + privileged: + type: boolean + runAsUser: + type: integer + procMount: + type: string + seccompProfile: + properties: + type: + type: string + localhostProfile: + type: string + type: object + type: object + name: + type: string + resources: + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: object + imagePullPolicy: + type: string + volumeDevices: + items: + properties: + devicePath: + type: string + name: + type: string + type: object + type: array + type: object + type: array + priorityClassName: + type: string + tolerations: + items: + properties: + key: + type: string + operator: + type: string + tolerationSeconds: + type: integer + value: + type: string + effect: + type: string + type: object + type: array + hostPID: + type: boolean + os: + properties: + name: + type: string + type: object + serviceAccountName: + type: string + shareProcessNamespace: + type: boolean + hostNetwork: + type: boolean + hostname: + type: string + nodeSelector: + additionalProperties: + type: string + type: object + enableServiceLinks: + type: boolean + affinity: + properties: + podAntiAffinity: + properties: + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + nodeAffinity: + properties: + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + weight: + type: integer + preference: + properties: + matchFields: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + type: object + type: object + type: array + requiredDuringSchedulingIgnoredDuringExecution: + properties: + nodeSelectorTerms: + items: + properties: + matchFields: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + type: object + type: array + type: object + type: object + podAffinity: + properties: + requiredDuringSchedulingIgnoredDuringExecution: + items: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + preferredDuringSchedulingIgnoredDuringExecution: + items: + properties: + podAffinityTerm: + properties: + namespaces: + items: + type: string + type: array + topologyKey: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + namespaceSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + weight: + type: integer + type: object + type: array + type: object + type: object + readinessGates: + items: + properties: + conditionType: + type: string + type: object + type: array + dnsPolicy: + type: string + hostIPC: + type: boolean + topologySpreadConstraints: + items: + properties: + topologyKey: + type: string + maxSkew: + type: integer + whenUnsatisfiable: + type: string + labelSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + values: + items: + type: string + type: array + operator: + type: string + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + type: array + overhead: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + schedulerName: + type: string + nodeName: + type: string + preemptionPolicy: + type: string + hostAliases: + items: + properties: + hostnames: + items: + type: string + type: array + ip: + type: string + type: object + type: array + runtimeClassName: + type: string + type: object + type: object + type: object + ingress: + description: |- + The deployment is, by default, exposed through a basic ingress. + You can change this behaviour by setting the enabled property to false. + properties: + enabled: + type: boolean + type: object + image: + description: Custom Keycloak image to be used. + type: string + imagePullSecrets: + description: Secret(s) that might be used when pulling an image from + a private container image registry or repository. + items: + properties: + name: + type: string + type: object + type: array + additionalOptions: + description: |- + Configuration of the Keycloak server. + expressed as a keys (reference: https://www.keycloak.org/server/all-config) and values that can be either direct values or references to secrets. + items: + properties: + secret: + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + value: + type: string + name: + type: string + type: object + type: array + db: + description: In this section you can find all properties related to + connect to a database. + properties: + passwordSecret: + description: The reference to a secret holding the password of + the database user. + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + usernameSecret: + description: The reference to a secret holding the username of + the database user. + properties: + optional: + type: boolean + key: + type: string + name: + type: string + type: object + port: + description: "Sets the port of the default JDBC URL of the chosen\ + \ vendor. If the `url` option is set, this option is ignored." + type: integer + schema: + description: The database schema to be used. + type: string + host: + description: "Sets the hostname of the default JDBC URL of the\ + \ chosen vendor. If the `url` option is set, this option is\ + \ ignored." + type: string + url: + description: "The full database JDBC URL. If not provided, a default\ + \ URL is set based on the selected database vendor. For instance,\ + \ if using 'postgres', the default JDBC URL would be 'jdbc:postgresql://localhost/keycloak'. " + type: string + poolInitialSize: + description: The initial size of the connection pool. + type: integer + poolMaxSize: + description: The maximum size of the connection pool. + type: integer + vendor: + description: The database vendor. + type: string + database: + description: "Sets the database name of the default JDBC URL of\ + \ the chosen vendor. If the `url` option is set, this option\ + \ is ignored." + type: string + poolMinSize: + description: The minimal size of the connection pool. + type: integer + type: object + features: + description: "In this section you can configure Keycloak features,\ + \ which should be enabled/disabled." + properties: + disabled: + description: Disabled Keycloak features + items: + type: string + type: array + enabled: + description: Enabled Keycloak features + items: + type: string + type: array + type: object + type: object + status: + properties: + conditions: + items: + properties: + status: + type: boolean + type: + type: string + message: + type: string + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/keycloak-operator/templates/kubernetes.yml b/charts/keycloak-operator/templates/kubernetes.yml new file mode 100644 index 0000000..bd24e05 --- /dev/null +++ b/charts/keycloak-operator/templates/kubernetes.yml @@ -0,0 +1,235 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: + app.quarkus.io/build-timestamp: 2023-01-12 - 16:26:45 +0000 + labels: + app.kubernetes.io/name: keycloak-operator + app.kubernetes.io/version: 20.0.3 + name: keycloak-operator +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + app.quarkus.io/build-timestamp: 2023-01-12 - 16:26:45 +0000 + labels: + app.kubernetes.io/name: keycloak-operator + app.kubernetes.io/version: 20.0.3 + name: keycloak-operator +spec: + ports: + - name: http + port: 80 + targetPort: 8080 + selector: + app.kubernetes.io/name: keycloak-operator + app.kubernetes.io/version: 20.0.3 + type: ClusterIP +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: keycloak-operator-role +rules: + - apiGroups: + - apps + - extensions + resources: + - statefulsets + verbs: + - get + - list + - watch + - create + - delete + - patch + - update + - apiGroups: + - "" + resources: + - secrets + - services + verbs: + - get + - list + - watch + - create + - delete + - patch + - update + - apiGroups: + - batch + resources: + - jobs + verbs: + - get + - list + - watch + - create + - delete + - patch + - update + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch + - create + - delete + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: keycloak-operator + name: keycloak-operator-role-binding +roleRef: + kind: Role + apiGroup: rbac.authorization.k8s.io + name: keycloak-operator-role +subjects: + - kind: ServiceAccount + name: keycloak-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keycloak-operator-view +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: view +subjects: + - kind: ServiceAccount + name: keycloak-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keycloakcontroller-role-binding +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: keycloakcontroller-cluster-role +subjects: + - kind: ServiceAccount + name: keycloak-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: keycloakrealmimportcontroller-role-binding +roleRef: + kind: ClusterRole + apiGroup: rbac.authorization.k8s.io + name: keycloakrealmimportcontroller-cluster-role +subjects: + - kind: ServiceAccount + name: keycloak-operator +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: keycloakcontroller-cluster-role +rules: + - apiGroups: + - k8s.keycloak.org + resources: + - keycloaks + - keycloaks/status + - keycloaks/finalizers + verbs: + - get + - list + - watch + - create + - delete + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: keycloakrealmimportcontroller-cluster-role +rules: + - apiGroups: + - k8s.keycloak.org + resources: + - keycloakrealmimports + - keycloakrealmimports/status + - keycloakrealmimports/finalizers + verbs: + - get + - list + - watch + - create + - delete + - patch + - update +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + app.quarkus.io/build-timestamp: 2023-01-12 - 16:26:45 +0000 + labels: + app.kubernetes.io/name: keycloak-operator + app.kubernetes.io/version: 20.0.3 + name: keycloak-operator +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: keycloak-operator + app.kubernetes.io/version: 20.0.3 + template: + metadata: + annotations: + app.quarkus.io/build-timestamp: 2023-01-12 - 16:26:45 +0000 + labels: + app.kubernetes.io/name: keycloak-operator + app.kubernetes.io/version: 20.0.3 + spec: + containers: + - env: + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_KEYCLOAK_IMAGE + value: quay.io/keycloak/keycloak:20.0.3 + image: quay.io/keycloak/keycloak-operator:20.0.3 + imagePullPolicy: Always + livenessProbe: + failureThreshold: 3 + httpGet: + path: /q/health/live + port: 8080 + scheme: HTTP + initialDelaySeconds: 0 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 10 + name: keycloak-operator + ports: + - containerPort: 8080 + name: http + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: /q/health/ready + port: 8080 + scheme: HTTP + initialDelaySeconds: 0 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 10 + serviceAccountName: keycloak-operator diff --git a/charts/keycloak/Chart.yaml b/charts/keycloak/Chart.yaml index 6feba38..4bb9ba3 100644 --- a/charts/keycloak/Chart.yaml +++ b/charts/keycloak/Chart.yaml @@ -3,17 +3,7 @@ apiVersion: "v2" name: "keycloak" version: "0" description: | - NOTE: This chart is enabled only when deploying DevOps Stack to clouds not providing OIDC out of the box. - - This chart installs Keycloak for the identity and access management. - It handles: - - * installation and configuration of Keycloak - * creating `devops-stack` realm and `jdoe` user account under a `devops-stack` realm - * adds the following DevOps stack apps as clients to OAuth2/OIDC authentication server: - * argocd - * grafana - * prometheus - * alertmanager - - NOTE: This chart must be instantiated in a sync-wave after the keycloak-operator chart. + This chart installs Keycloak for the identity and access management. It needs to be deployed after the chart that + deploys the Keycloak Operator. + It is mainly used for the deployment of OIDC in test Kubernetes clusters that do not provide OIDC out-of-the-box, + such as KinD and K3s. diff --git a/charts/keycloak/templates/_helpers.tpl b/charts/keycloak/templates/_helpers.tpl new file mode 100644 index 0000000..bb7b560 --- /dev/null +++ b/charts/keycloak/templates/_helpers.tpl @@ -0,0 +1,21 @@ +{{/* Create chart name and version to be used by the chart label */}} +{{- define "devops-stack-module-keycloak.chart" }} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{ end -}} + +{{/* Define selectors to be used (to be also used as templates) */}} +{{- define "devops-stack-module-keycloak.selector" }} +app.kubernetes.io/name: {{ .Chart.Name }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{ end -}} + +{{/* Define common labels */}} +{{- define "devops-stack-module-keycloak.labels" -}} +{{ include "devops-stack-module-keycloak.selector" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{ end -}} +app.kubernetes.io/managed-by: {{ .Release.Service }} +helm.sh/chart: {{ include "devops-stack-module-keycloak.chart" . }} +{{- end -}} +{{/* End labels */}} diff --git a/charts/keycloak/templates/ingress.yaml b/charts/keycloak/templates/ingress.yaml deleted file mode 100644 index deabdca..0000000 --- a/charts/keycloak/templates/ingress.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: keycloak - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if .Values.ingress.tls }} - tls: - {{- range .Values.ingress.tls }} - - hosts: - {{- range .hosts }} - - {{ . | quote }} - {{- end }} - secretName: {{ .secretName }} - {{- end }} - {{- end }} - rules: - {{- range .Values.ingress.hosts }} - - host: {{ .host | quote }} - http: - paths: - {{- range .paths }} - - path: {{ . }} - pathType: Prefix - backend: - service: - name: keycloak-ingress - port: - number: 8443 - {{- end }} - {{- end }} diff --git a/charts/keycloak/templates/keycloak.yaml b/charts/keycloak/templates/keycloak.yaml index 303c584..2dd8043 100644 --- a/charts/keycloak/templates/keycloak.yaml +++ b/charts/keycloak/templates/keycloak.yaml @@ -1,21 +1,24 @@ --- -apiVersion: "keycloak.org/v1alpha1" -kind: "Keycloak" +apiVersion: k8s.keycloak.org/v2alpha1 +kind: Keycloak metadata: - annotations: - argocd.argoproj.io/sync-options: "SkipDryRunOnMissingResource=true" - name: "keycloak" + name: keycloak labels: - app: "sso" + {{- include "devops-stack-module-keycloak.labels" $ | indent 4 }} spec: - externalDatabase: - enabled: true instances: 1 - keycloakDeploymentSpec: - experimental: - env: - - name: "DB_VENDOR" - value: "h2" - - name: "DB_ADDR" - - name: "DB_USER" - - name: "DB_PASSWORD" + db: + vendor: {{ .Values.keycloak.database.vendor }} + host: {{ .Values.keycloak.database.host }} + usernameSecret: + name: keycloak-db-secret + key: username + passwordSecret: + name: keycloak-db-secret + key: password + http: + httpEnabled: true # Use HTTP since we use Traefik as a reverse proxy for HTTPS + hostname: + strict: false + ingress: + enabled: false diff --git a/charts/keycloak/templates/keycloak_clusterip.yaml b/charts/keycloak/templates/keycloak_clusterip.yaml new file mode 100644 index 0000000..1931a3a --- /dev/null +++ b/charts/keycloak/templates/keycloak_clusterip.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: keycloak + labels: + {{- include "devops-stack-module-keycloak.labels" $ | indent 4 }} +spec: + type: ClusterIP + ports: + - name: http + port: 8080 + targetPort: 8080 + selector: + app: keycloak diff --git a/charts/keycloak/templates/keycloak_db.yaml b/charts/keycloak/templates/keycloak_db.yaml new file mode 100644 index 0000000..3b38707 --- /dev/null +++ b/charts/keycloak/templates/keycloak_db.yaml @@ -0,0 +1,63 @@ +{{- if .Values.keycloak.database.create }} +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Values.keycloak.database.host }} + labels: + {{- include "devops-stack-module-keycloak.labels" $ | indent 4 }} +spec: + serviceName: postgresql-db-service + selector: + matchLabels: + app: {{ .Values.keycloak.database.host }} + replicas: 1 + template: + metadata: + labels: + app: {{ .Values.keycloak.database.host }} + spec: + containers: + - name: postgresql-db + image: postgres:13-alpine + env: + - name: POSTGRES_USER + valueFrom: + secretKeyRef: + name: keycloak-db-secret + key: username + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: keycloak-db-secret + key: password + - name: PGDATA + value: /data/pgdata + - name: POSTGRES_DB + value: keycloak +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.keycloak.database.host }} + labels: + {{- include "devops-stack-module-keycloak.labels" $ | indent 4 }} +spec: + selector: + app: {{ .Values.keycloak.database.host }} + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 +--- +apiVersion: v1 +kind: Secret +metadata: + name: keycloak-db-secret + labels: + {{- include "devops-stack-module-keycloak.labels" $ | indent 4 }} +type: Opaque +data: + username: {{ .Values.keycloak.database.username }} + password: {{ .Values.keycloak.database.password }} +{{- end }} diff --git a/charts/keycloak/templates/keycloak_ingress.yaml b/charts/keycloak/templates/keycloak_ingress.yaml new file mode 100644 index 0000000..a816951 --- /dev/null +++ b/charts/keycloak/templates/keycloak_ingress.yaml @@ -0,0 +1,35 @@ +--- +{{ if .Values.keycloak.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: keycloak + {{- with .Values.keycloak.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + rules: + {{- range .Values.keycloak.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + - path: {{ .path | quote }} + pathType: Prefix + backend: + service: + name: keycloak + port: + number: 8080 + {{- end }} + {{- if .Values.keycloak.ingress.tls }} + tls: + {{- range .Values.keycloak.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} +{{ end }} diff --git a/charts/keycloak/templates/keycloakclient.yaml b/charts/keycloak/templates/keycloakclient.yaml deleted file mode 100644 index f134848..0000000 --- a/charts/keycloak/templates/keycloakclient.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -apiVersion: "keycloak.org/v1alpha1" -kind: "KeycloakClient" -metadata: - annotations: - argocd.argoproj.io/sync-options: "SkipDryRunOnMissingResource=true" - name: "devops-stack-applications" - labels: - app: "sso" -spec: - realmSelector: - matchLabels: - app: "sso" - client: - clientId: "{{ .Values.keycloakClient.client.clientId }}" - secret: "{{ .Values.keycloakClient.client.secret }}" - clientAuthenticatorType: "client-secret" - standardFlowEnabled: true - redirectUris: - {{- toYaml .Values.keycloakClient.client.redirectUris | nindent 6}} - defaultClientScopes: - - "email" - - "profile" diff --git a/charts/keycloak/templates/keycloakrealm.yaml b/charts/keycloak/templates/keycloakrealm.yaml deleted file mode 100644 index 170037a..0000000 --- a/charts/keycloak/templates/keycloakrealm.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -apiVersion: keycloak.org/v1alpha1 -kind: KeycloakRealm -metadata: - annotations: - argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true - name: devops-stack - labels: - app: sso -spec: - realm: - id: devops-stack - realm: devops-stack - enabled: true - displayName: devops-stack Realm - instanceSelector: - matchLabels: - app: sso diff --git a/charts/keycloak/templates/keycloakuser.yaml b/charts/keycloak/templates/keycloakuser.yaml deleted file mode 100644 index c738a69..0000000 --- a/charts/keycloak/templates/keycloakuser.yaml +++ /dev/null @@ -1,29 +0,0 @@ -{{- range $username, $infos := .Values.keycloakUsers }} ---- -apiVersion: keycloak.org/v1alpha1 -kind: KeycloakUser -metadata: - annotations: - argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true - name: {{ $username }} - labels: - app: sso -spec: - user: - username: {{ $username }} - firstName: {{ $infos.first_name }} - lastName: {{ $infos.name }} - email: {{ $infos.email | quote }} - enabled: true - emailVerified: true - credentials: - - temporary: false - type: password - value: {{ $infos.password | quote }} - clientRoles: - account: - - manage-account - realmSelector: - matchLabels: - app: sso -{{- end }} diff --git a/charts/keycloak/templates/service.yaml b/charts/keycloak/templates/service.yaml deleted file mode 100644 index bb1f2eb..0000000 --- a/charts/keycloak/templates/service.yaml +++ /dev/null @@ -1,19 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: keycloak-ingress - {{- with .Values.service.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - type: ClusterIP - ports: - - port: 8443 - targetPort: 8443 - protocol: TCP - name: keycloak - selector: - app: keycloak - component: keycloak diff --git a/charts/keycloak/values.yaml b/charts/keycloak/values.yaml index 48f07a4..ed97d53 100644 --- a/charts/keycloak/values.yaml +++ b/charts/keycloak/values.yaml @@ -1,3 +1 @@ --- -# keycloak: -# baseDomain: ... diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 0000000..41a0f0f --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,7 @@ +--- +name: "keycloak" +title: "Keycloak Module" +version: true +start_page: README.adoc +nav: + - "modules/ROOT/nav.adoc" diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc new file mode 100644 index 0000000..5be79da --- /dev/null +++ b/docs/modules/ROOT/nav.adoc @@ -0,0 +1,4 @@ +* xref:ROOT:README.adoc[Home] +* xref:ROOT:oidc_bootstrap/README.adoc[Realm Bootstrap Submodule] +* https://github.com/camptocamp/devops-stack-module-keycloak[Repository,window=_blank] +* xref:ROOT:ROOT:index.adoc[_Return to DevOps Stack docs_] diff --git a/docs/modules/ROOT/pages/README.adoc b/docs/modules/ROOT/pages/README.adoc new file mode 120000 index 0000000..2cfd4f7 --- /dev/null +++ b/docs/modules/ROOT/pages/README.adoc @@ -0,0 +1 @@ +../../../../README.adoc \ No newline at end of file diff --git a/docs/modules/ROOT/pages/oidc_bootstrap/README.adoc b/docs/modules/ROOT/pages/oidc_bootstrap/README.adoc new file mode 120000 index 0000000..a8a4bc8 --- /dev/null +++ b/docs/modules/ROOT/pages/oidc_bootstrap/README.adoc @@ -0,0 +1 @@ +../../../../../oidc_bootstrap/README.adoc \ No newline at end of file diff --git a/local.tf b/local.tf deleted file mode 100644 index 9b38fb8..0000000 --- a/local.tf +++ /dev/null @@ -1,42 +0,0 @@ -locals { - keycloak_defaults = { - user_map = { - jdoe = { - name = "Doe" - first_name = "John" - email = "jdoe@example.com" - } - } - domain = "keycloak.apps.${var.cluster_name}.${var.base_domain}" - } - - keycloak = merge( - local.keycloak_defaults, - var.keycloak, - ) - - user_map = { for username, infos in local.keycloak.user_map : username => merge(infos, tomap({ password = random_password.keycloak_passwords[username].result })) } - - - oidc = { - issuer_url = format("https://keycloak.apps.%s.%s/auth/realms/devops-stack", var.cluster_name, var.base_domain) - oauth_url = format("https://keycloak.apps.%s.%s/auth/realms/devops-stack/protocol/openid-connect/auth", var.cluster_name, var.base_domain) - token_url = format("https://keycloak.apps.%s.%s/auth/realms/devops-stack/protocol/openid-connect/token", var.cluster_name, var.base_domain) - api_url = format("https://keycloak.apps.%s.%s/auth/realms/devops-stack/protocol/openid-connect/userinfo", var.cluster_name, var.base_domain) - client_id = "devops-stack-applications" - client_secret = random_password.clientsecret.result - oauth2_proxy_extra_args = [ - "--insecure-oidc-skip-issuer-verification=true", - "--ssl-insecure-skip-verify=true", - ] - } - - default_yaml = [templatefile("${path.module}/values.tmpl.yaml", { - oidc = local.oidc, - base_domain = var.base_domain, - cluster_issuer = var.cluster_issuer, - keycloak = local.keycloak, - user_map = local.user_map - })] - all_yaml = concat(local.default_yaml, var.extra_yaml) -} diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..7389b70 --- /dev/null +++ b/locals.tf @@ -0,0 +1,49 @@ +locals { + helm_values = [{ + keycloak = { + name = "keycloak" + # Database creds are shown in tfm plan. + # TODO manage this. Proposal: create namespace and secret before app. + database = var.database != null ? merge(var.database, { + create = false + username = base64encode(var.database.username) + password = base64encode(var.database.password) + }) : { + # TODO doc that the fallback map (experimental ephemeral postgresql server) should never be used in production. + create = true + vendor = "postgres" + username = base64encode("postgres") + password = base64encode(random_password.db_password.0.result) + host = "keycloak-postgres-db" + } + ingress = { + enabled = true + annotations = { + "cert-manager.io/cluster-issuer" = "${var.cluster_issuer}" + "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" + "traefik.ingress.kubernetes.io/router.middlewares" = "traefik-withclustername@kubernetescrd" + "traefik.ingress.kubernetes.io/router.tls" = "true" + "ingress.kubernetes.io/ssl-redirect" = "true" + "kubernetes.io/ingress.allow-http" = "false" + } + hosts = [ + { + host = "keycloak.apps.${var.base_domain}" + path = "/" + }, + { + host = "keycloak.apps.${var.cluster_name}.${var.base_domain}" + path = "/" + }, + ] + tls = [{ + secretName = "keycloak-tls" + hosts = [ + "keycloak.apps.${var.base_domain}", + "keycloak.apps.${var.cluster_name}.${var.base_domain}" + ] + }] + } + } + }] +} diff --git a/main.tf b/main.tf index 6a6e502..c796ca9 100644 --- a/main.tf +++ b/main.tf @@ -2,20 +2,22 @@ resource "null_resource" "dependencies" { triggers = var.dependency_ids } +resource "random_password" "db_password" { + count = var.database == null ? 1 : 0 + length = 32 + special = false +} + resource "argocd_project" "this" { metadata { name = "keycloak" - namespace = var.argocd.namespace - annotations = { - "devops-stack.io/argocd_namespace" = var.argocd.namespace - } + namespace = var.argocd_namespace } spec { - description = "keycloak application project" + description = "Keycloak application project" source_repos = [ "https://github.com/camptocamp/devops-stack-module-keycloak.git", - "https://github.com/keycloak/keycloak-operator.git" ] destination { @@ -35,24 +37,24 @@ resource "argocd_project" "this" { } data "utils_deep_merge_yaml" "values" { - input = local.all_yaml + input = [for i in concat(local.helm_values, var.helm_values) : yamlencode(i)] } resource "argocd_application" "operator" { metadata { name = "keycloak-operator" - namespace = var.argocd.namespace + namespace = var.argocd_namespace } - wait = true + wait = var.app_autosync == { "allow_empty" = tobool(null), "prune" = tobool(null), "self_heal" = tobool(null) } ? false : true spec { project = argocd_project.this.metadata.0.name source { - repo_url = "https://github.com/keycloak/keycloak-operator.git" - path = "deploy" - target_revision = "15.0.1" + repo_url = "https://github.com/camptocamp/devops-stack-module-keycloak.git" + path = "charts/keycloak-operator" + target_revision = var.target_revision } destination { @@ -61,9 +63,14 @@ resource "argocd_application" "operator" { } sync_policy { - automated = { - prune = true - self_heal = true + automated = var.app_autosync + + retry { + backoff = { + duration = "" + max_duration = "" + } + limit = "0" } sync_options = [ @@ -80,9 +87,16 @@ resource "argocd_application" "operator" { resource "argocd_application" "this" { metadata { name = "keycloak" - namespace = var.argocd.namespace + namespace = var.argocd_namespace } + timeouts { + create = "15m" + delete = "15m" + } + + wait = var.app_autosync == { "allow_empty" = tobool(null), "prune" = tobool(null), "self_heal" = tobool(null) } ? false : true + spec { project = argocd_project.this.metadata.0.name @@ -101,9 +115,14 @@ resource "argocd_application" "this" { } sync_policy { - automated = { - prune = true - self_heal = true + automated = var.app_autosync + + retry { + backoff = { + duration = "" + max_duration = "" + } + limit = "0" } sync_options = [ @@ -112,29 +131,40 @@ resource "argocd_application" "this" { } } - depends_on = [argocd_application.operator] + depends_on = [ + resource.argocd_application.operator, + ] } -resource "random_password" "clientsecret" { - length = 16 - special = false + +resource "null_resource" "wait_for_keycloak" { + # Until curl successfully completes the requested transfer, wait 10 seconds and retry for 180 seconds until timeout. + # --retry-all-errors makes curl retry even on non-transient HTTP errors (e.g. 404), requires curl >= 7.71.0 + # -f makes curl fail on server errors + # -s prevents it from printing messages and the progress meter + # -o /dev/null discards the output message + # -k ignores self-signed SSL certificates + provisioner "local-exec" { + command = "curl --retry 20 --retry-max-time 180 --retry-delay 10 --retry-all-errors -f -s -o /dev/null -k 'https://keycloak.apps.${var.cluster_name}.${var.base_domain}'" + } + + depends_on = [ + resource.argocd_application.this, + ] } -#data "kubernetes_secret" "keycloak_admin_password" { -# metadata { -# name = "credential-keycloak" -# namespace = "keycloak" -# } -#} - -resource "random_password" "keycloak_passwords" { - for_each = local.keycloak.user_map - length = 16 - special = false +data "kubernetes_secret" "admin_credentials" { + metadata { + name = "keycloak-initial-admin" + namespace = var.namespace + } + depends_on = [ + resource.null_resource.wait_for_keycloak, + ] } resource "null_resource" "this" { depends_on = [ - resource.argocd_application.this, + resource.null_resource.wait_for_keycloak, ] } diff --git a/oidc_bootstrap/README.adoc b/oidc_bootstrap/README.adoc new file mode 100644 index 0000000..3a9aef6 --- /dev/null +++ b/oidc_bootstrap/README.adoc @@ -0,0 +1,416 @@ += OIDC Bootstrap + +A https://devops-stack.io[DevOps Stack] module to bootstrap a realm, an administrators group with one or more users and an OIDC client in order to use https://www.keycloak.org/[Keycloak] as an OIDC provider. + +This module allows you to have a working authentication provider for the DevOps Stack without having to configure Keycloak manually. + +IMPORTANT: Because the main use of this module is to have a working Keycloak instance in a development environment, it provides a sensible configuration with some secure enough defaults. However, *it is not recommended to be used in a production environment*. For that purpose, we recommend you simply use this module as an example. Take a look at the code and read the https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs[provider's documentation] to get an idea on how it can be used manage your Keycloak instance. + +== Usage + +After deploying Keycloak using the main module on this repository, first you need to add the provider configuration necessary on your root module: + +[source,terraform] +---- +terraform { + required_providers { + keycloak = { + source = "mrparkers/keycloak" + } + } +} + +provider "keycloak" { + client_id = "admin-cli" + username = module.keycloak.admin_credentials.username + password = module.keycloak.admin_credentials.password + url = "https://keycloak.apps.${local.cluster_name}.${format("%s.nip.io", replace(module.ingress.external_ip, ".", "-"))}" + initial_login = false # Do no try to setup the provider before Keycloak is provisioned. + tls_insecure_skip_verify = true # Since we are in a testing environment, do not verify the authenticity of SSL certificates. +} +---- + +NOTE: The argument `initial_login` absolutely needs to be set as `false`, otherwise Terraform will try to connect to Keycloak before it is deployed. The argument `tls_insecure_skip_verify` needs to be set as `false` only on testing environments, when using self-signed SSL certificates. + +After setting up the provider, you can then bootstrap the authentication configuration like this: + +[source, terraform] +---- +module "oidc" { + source = "git::https://github.com/camptocamp/devops-stack-module-keycloak.git//oidc_bootstrap?ref=" + + cluster_name = local.cluster_name + base_domain = format("%s.nip.io", replace(module.ingress.external_ip, ".", "-")) + + dependency_ids = { + keycloak = module.keycloak.id + } +} +---- + +=== User Configuration + +By default, the `oicd_bootstrap` module creates a basic realm containing a placeholder user that you can use out-of-the-box to authenticate to the other applications on the DevOps Stack. + +However, you can provide a map of desired users and the submodule creates them all with an initial password that can then be changed. + +Simply declare the module as follows: + +[source, terraform] +---- +module "oidc" { + source = "git::https://github.com/camptocamp/devops-stack-module-keycloak.git//oidc_bootstrap?ref=" + + cluster_name = local.cluster_name + base_domain = format("%s.nip.io", replace(module.ingress.external_ip, ".", "-")) + + user_map = { + johndoe = { + username = "johndoe" + first_name = "John" + last_name = "Doe" + email = "john.doe@example.com" + } + janedoe = { + username = "janedoe" + first_name = "Jane" + last_name = "Doe" + email = "jane.doe@example.com" + } + } + + dependency_ids = { + keycloak = module.keycloak.id + } +} +---- + +NOTE: All the fields on each user are required. Besides, since the e-mail is a scope required by most of our apps, the e-mail is automatically set as verified when the users are created. + +IMPORTANT: All users will belong to the administrators group and will have high privileges in applications such as Argo CD. + +The module contains an output called `devops_stack_users_passwords` where you can get a map containing every username and their respective initial password. + +=== OIDC Configuration + +By default, the OIDC client is configured to allow returning to any URL after the authentication is successful. If you prefer, you can restrict only the redirect URIs to a list of domains using the input variable `oidc_redirect_uris`: + +[source, terraform] +---- +module "oidc" { + source = "git::https://github.com/camptocamp/devops-stack-module-keycloak.git//oidc_bootstrap?ref=" + + cluster_name = local.cluster_name + base_domain = format("%s.nip.io", replace(module.ingress.external_ip, ".", "-")) + + oidc_redirec_uris = [ + "https://argocd.apps.${local.cluster_name}.${format("%s.nip.io", replace(module.ingress.external_ip, ".", "-"))}/auth/callback", + "https://grafana.apps.${local.cluster_name}.${format("%s.nip.io", replace(module.ingress.external_ip, ".", "-"))}/login/generic_oauth", + "https://prometheus.apps.${local.cluster_name}.${format("%s.nip.io", replace(module.ingress.external_ip, ".", "-"))}/oauth2/callback", + "https://thanos-query.apps.${local.cluster_name}.${format("%s.nip.io", replace(module.ingress.external_ip, ".", "-"))}/oauth2/callback", + "https://thanos-bucketweb.apps.${local.cluster_name}.${format("%s.nip.io", replace(module.ingress.external_ip, ".", "-"))}/oauth2/callback", + "https://alertmanager.apps.${local.cluster_name}.${format("%s.nip.io", replace(module.ingress.external_ip, ".", "-"))}/oauth2/callback", + ] + + dependency_ids = { + keycloak = module.keycloak.id + } +} +---- + +The module provides and output called `oidc` containing the OIDC configuration that is to be passed on to other modules. This output is an object that outputs the content of `local.oidc`: + +[source, terraform] +---- +locals { + oidc = { + issuer_url = format("https://keycloak.apps.%s.%s/realms/devops-stack", var.cluster_name, var.base_domain) + oauth_url = format("https://keycloak.apps.%s.%s/realms/devops-stack/protocol/openid-connect/auth", var.cluster_name, var.base_domain) + token_url = format("https://keycloak.apps.%s.%s/realms/devops-stack/protocol/openid-connect/token", var.cluster_name, var.base_domain) + api_url = format("https://keycloak.apps.%s.%s/realms/devops-stack/protocol/openid-connect/userinfo", var.cluster_name, var.base_domain) + client_id = "devops-stack-applications" + client_secret = resource.random_password.client_secret.result + oauth2_proxy_extra_args = var.cluster_issuer == "ca-issuer" ? [ + "--insecure-oidc-skip-issuer-verification=true", + "--ssl-insecure-skip-verify=true", + ] : [] + } +} +---- + +== Technical Reference + +=== Dependencies + +==== `module.keycloak` + +Obviously, this module must be deployed after `module.keycloak`, because it needs a working Keycloak instance where to create its resources. + +// BEGIN_TF_DOCS +=== Requirements + +The following requirements are needed by this module: + +- [[requirement_keycloak]] <> (~> 4) + +- [[requirement_null]] <> (~> 3) + +- [[requirement_random]] <> (~> 3) + +=== Providers + +The following providers are used by this module: + +- [[provider_keycloak]] <> (~> 4) + +- [[provider_null]] <> (~> 3) + +- [[provider_random]] <> (~> 3) + +=== Modules + +No modules. + +=== Resources + +The following resources are used by this module: + +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/group[keycloak_group.devops_stack_admins] (resource) +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_client[keycloak_openid_client.devops_stack] (resource) +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_client_default_scopes[keycloak_openid_client_default_scopes.client_default_scopes] (resource) +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_client_scope[keycloak_openid_client_scope.devops_stack] (resource) +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_group_membership_protocol_mapper[keycloak_openid_group_membership_protocol_mapper.devops_stack] (resource) +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/realm[keycloak_realm.devops_stack] (resource) +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/user[keycloak_user.devops_stack_users] (resource) +- https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/user_groups[keycloak_user_groups.devops_stack_admins] (resource) +- https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.dependencies] (resource) +- https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.this] (resource) +- https://registry.terraform.io/providers/random/latest/docs/resources/password[random_password.client_secret] (resource) +- https://registry.terraform.io/providers/random/latest/docs/resources/password[random_password.devops_stack_users] (resource) + +=== Required Inputs + +The following input variables are required: + +==== [[input_base_domain]] <> + +Description: Base domain of the cluster. Value used for the ingress' URL of the application. + +Type: `string` + +==== [[input_cluster_name]] <> + +Description: Name given to the cluster. Value used for the ingress' URL of the application. + +Type: `string` + +=== Optional Inputs + +The following input variables are optional (have default values): + +==== [[input_cluster_issuer]] <> + +Description: SSL certificate issuer to use. In this module it is used to conditionally add extra arguments to the OIDC configuration. + +Type: `string` + +Default: `"ca-issuer"` + +==== [[input_dependency_ids]] <> + +Description: IDs of the other modules on which this module depends on. + +Type: `map(string)` + +Default: `{}` + +==== [[input_oidc_redirect_uris]] <> + +Description: List of URIs where the authentication server is allowed to return during the authentication flow. + +Type: `list(string)` + +Default: +[source,json] +---- +[ + "*" +] +---- + +==== [[input_user_map]] <> + +Description: List of users to be added to the DevOps Stack Realm. Note that all fields are mandatory. + +Type: +[source,hcl] +---- +map(object({ + username = string + email = string + first_name = string + last_name = string + })) +---- + +Default: +[source,json] +---- +{ + "devopsadmin": { + "email": "devopsadmin@devops-stack.io", + "first_name": "Administrator", + "last_name": "DevOps Stack", + "username": "devopsadmin" + } +} +---- + +=== Outputs + +The following outputs are exported: + +==== [[output_devops_stack_users_passwords]] <> + +Description: Map containing the credentials of each created user. + +==== [[output_id]] <> + +Description: ID to pass other modules in order to refer to this module as a dependency. + +==== [[output_oidc]] <> + +Description: Object containing multiple OIDC configuration values. +// END_TF_DOCS + +=== Reference in table format + +.Show tables +[%collapsible] +==== +// BEGIN_TF_TABLES += Requirements + +[cols="a,a",options="header,autowidth"] +|=== +|Name |Version +|[[requirement_keycloak]] <> |~> 4 +|[[requirement_null]] <> |~> 3 +|[[requirement_random]] <> |~> 3 +|=== + += Providers + +[cols="a,a",options="header,autowidth"] +|=== +|Name |Version +|[[provider_keycloak]] <> |~> 4 +|[[provider_null]] <> |~> 3 +|[[provider_random]] <> |~> 3 +|=== + += Resources + +[cols="a,a",options="header,autowidth"] +|=== +|Name |Type +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/group[keycloak_group.devops_stack_admins] |resource +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_client[keycloak_openid_client.devops_stack] |resource +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_client_default_scopes[keycloak_openid_client_default_scopes.client_default_scopes] |resource +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_client_scope[keycloak_openid_client_scope.devops_stack] |resource +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/openid_group_membership_protocol_mapper[keycloak_openid_group_membership_protocol_mapper.devops_stack] |resource +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/realm[keycloak_realm.devops_stack] |resource +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/user[keycloak_user.devops_stack_users] |resource +|https://registry.terraform.io/providers/mrparkers/keycloak/latest/docs/resources/user_groups[keycloak_user_groups.devops_stack_admins] |resource +|https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.dependencies] |resource +|https://registry.terraform.io/providers/null/latest/docs/resources/resource[null_resource.this] |resource +|https://registry.terraform.io/providers/random/latest/docs/resources/password[random_password.client_secret] |resource +|https://registry.terraform.io/providers/random/latest/docs/resources/password[random_password.devops_stack_users] |resource +|=== + += Inputs + +[cols="a,a,a,a,a",options="header,autowidth"] +|=== +|Name |Description |Type |Default |Required +|[[input_base_domain]] <> +|Base domain of the cluster. Value used for the ingress' URL of the application. +|`string` +|n/a +|yes + +|[[input_cluster_issuer]] <> +|SSL certificate issuer to use. In this module it is used to conditionally add extra arguments to the OIDC configuration. +|`string` +|`"ca-issuer"` +|no + +|[[input_cluster_name]] <> +|Name given to the cluster. Value used for the ingress' URL of the application. +|`string` +|n/a +|yes + +|[[input_dependency_ids]] <> +|IDs of the other modules on which this module depends on. +|`map(string)` +|`{}` +|no + +|[[input_oidc_redirect_uris]] <> +|List of URIs where the authentication server is allowed to return during the authentication flow. +|`list(string)` +| + +[source] +---- +[ + "*" +] +---- + +|no + +|[[input_user_map]] <> +|List of users to be added to the DevOps Stack Realm. Note that all fields are mandatory. +| + +[source] +---- +map(object({ + username = string + email = string + first_name = string + last_name = string + })) +---- + +| + +[source] +---- +{ + "devopsadmin": { + "email": "devopsadmin@devops-stack.io", + "first_name": "Administrator", + "last_name": "DevOps Stack", + "username": "devopsadmin" + } +} +---- + +|no + +|=== + += Outputs + +[cols="a,a",options="header,autowidth"] +|=== +|Name |Description +|[[output_devops_stack_users_passwords]] <> |Map containing the credentials of each created user. +|[[output_id]] <> |ID to pass other modules in order to refer to this module as a dependency. +|[[output_oidc]] <> |Object containing multiple OIDC configuration values. +|=== +// END_TF_TABLES +==== diff --git a/oidc_bootstrap/locals.tf b/oidc_bootstrap/locals.tf new file mode 100644 index 0000000..6b02988 --- /dev/null +++ b/oidc_bootstrap/locals.tf @@ -0,0 +1,14 @@ +locals { + oidc = { + issuer_url = format("https://keycloak.apps.%s.%s/realms/devops-stack", var.cluster_name, var.base_domain) + oauth_url = format("https://keycloak.apps.%s.%s/realms/devops-stack/protocol/openid-connect/auth", var.cluster_name, var.base_domain) + token_url = format("https://keycloak.apps.%s.%s/realms/devops-stack/protocol/openid-connect/token", var.cluster_name, var.base_domain) + api_url = format("https://keycloak.apps.%s.%s/realms/devops-stack/protocol/openid-connect/userinfo", var.cluster_name, var.base_domain) + client_id = "devops-stack-applications" + client_secret = resource.random_password.client_secret.result + oauth2_proxy_extra_args = var.cluster_issuer == "ca-issuer" ? [ + "--insecure-oidc-skip-issuer-verification=true", + "--ssl-insecure-skip-verify=true", + ] : [] + } +} diff --git a/oidc_bootstrap/main.tf b/oidc_bootstrap/main.tf new file mode 100644 index 0000000..efd6f78 --- /dev/null +++ b/oidc_bootstrap/main.tf @@ -0,0 +1,109 @@ +resource "null_resource" "dependencies" { + triggers = var.dependency_ids +} + +resource "keycloak_realm" "devops_stack" { + realm = "devops-stack" + display_name = "DevOps Stack" + display_name_html = "DevOps Stack Logo" + login_with_email_allowed = true + access_code_lifespan = "1h" + ssl_required = "external" + password_policy = "upperCase(1) and length(8) and forceExpiredPasswordChange(365) and notUsername" + attributes = { + terraform = "true" + } + + depends_on = [ + resource.null_resource.dependencies + ] +} + +resource "random_password" "client_secret" { + length = 32 + special = false +} + +resource "keycloak_openid_client" "devops_stack" { + realm_id = resource.keycloak_realm.devops_stack.id + name = "DevOps Stack Applications" + client_id = local.oidc.client_id + client_secret = resource.random_password.client_secret.result + access_type = "CONFIDENTIAL" + standard_flow_enabled = true + direct_access_grants_enabled = true + valid_redirect_uris = var.oidc_redirect_uris +} + +resource "keycloak_openid_client_scope" "devops_stack" { + realm_id = resource.keycloak_realm.devops_stack.id + name = "groups" + description = "OpenID Connect scope to map a user's group memberships to a claim" + include_in_token_scope = true +} + +resource "keycloak_openid_group_membership_protocol_mapper" "devops_stack" { + realm_id = resource.keycloak_realm.devops_stack.id + client_scope_id = resource.keycloak_openid_client_scope.devops_stack.id + name = "groups" + claim_name = "groups" + full_path = false +} + +resource "keycloak_openid_client_default_scopes" "client_default_scopes" { + realm_id = resource.keycloak_realm.devops_stack.id + client_id = resource.keycloak_openid_client.devops_stack.id + default_scopes = [ + "profile", + "email", + "roles", + resource.keycloak_openid_client_scope.devops_stack.name, + ] +} + +resource "keycloak_group" "devops_stack_admins" { + realm_id = resource.keycloak_realm.devops_stack.id + name = "devops-stack-admins" +} + +resource "random_password" "devops_stack_users" { + for_each = var.user_map + + length = 32 + special = false +} + +resource "keycloak_user" "devops_stack_users" { + for_each = var.user_map + + realm_id = resource.keycloak_realm.devops_stack.id + username = each.value.username + initial_password { + value = resource.random_password.devops_stack_users[each.key].result + } + first_name = each.value.first_name + last_name = each.value.last_name + email = each.value.email + email_verified = true + attributes = { + terraform = "true" + } +} + +resource "keycloak_user_groups" "devops_stack_admins" { + for_each = var.user_map + + user_id = resource.keycloak_user.devops_stack_users[each.key].id + realm_id = resource.keycloak_realm.devops_stack.id + group_ids = [ + resource.keycloak_group.devops_stack_admins.id + ] +} + +resource "null_resource" "this" { + depends_on = [ + resource.keycloak_realm.devops_stack, + resource.keycloak_group.devops_stack_admins, + resource.keycloak_user_groups.devops_stack_admins, + ] +} diff --git a/oidc_bootstrap/output.tf b/oidc_bootstrap/output.tf new file mode 100644 index 0000000..471287d --- /dev/null +++ b/oidc_bootstrap/output.tf @@ -0,0 +1,18 @@ +output "id" { + description = "ID to pass other modules in order to refer to this module as a dependency." + value = resource.null_resource.this.id +} + +output "oidc" { + description = "Object containing multiple OIDC configuration values." + value = local.oidc + sensitive = true +} + +output "devops_stack_users_passwords" { + description = "Map containing the credentials of each created user." + value = { + for key, value in var.user_map : value.username => resource.random_password.devops_stack_users[key].result + } + sensitive = true +} diff --git a/oidc_bootstrap/terraform.tf b/oidc_bootstrap/terraform.tf new file mode 100644 index 0000000..5264abc --- /dev/null +++ b/oidc_bootstrap/terraform.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + keycloak = { + source = "mrparkers/keycloak" + version = "~> 4" + } + random = { + source = "random" + version = "~> 3" + } + null = { + source = "null" + version = "~> 3" + } + } +} diff --git a/oidc_bootstrap/variables.tf b/oidc_bootstrap/variables.tf new file mode 100644 index 0000000..0229100 --- /dev/null +++ b/oidc_bootstrap/variables.tf @@ -0,0 +1,55 @@ +####################### +## Standard variables +####################### + +variable "cluster_name" { + description = "Name given to the cluster. Value used for the ingress' URL of the application." + type = string +} + +variable "base_domain" { + description = "Base domain of the cluster. Value used for the ingress' URL of the application." + type = string +} + +variable "cluster_issuer" { + description = "SSL certificate issuer to use. In this module it is used to conditionally add extra arguments to the OIDC configuration." + type = string + default = "ca-issuer" +} + +variable "dependency_ids" { + description = "IDs of the other modules on which this module depends on." + type = map(string) + default = {} +} + +####################### +## Module variables +####################### + +variable "oidc_redirect_uris" { + description = "List of URIs where the authentication server is allowed to return during the authentication flow." + type = list(string) + default = [ + "*" + ] +} + +variable "user_map" { + description = "List of users to be added to the DevOps Stack Realm. Note that all fields are mandatory." + type = map(object({ + username = string + email = string + first_name = string + last_name = string + })) + default = { + devopsadmin = { + username = "devopsadmin" + email = "devopsadmin@devops-stack.io" + first_name = "Administrator" + last_name = "DevOps Stack" + } + } +} diff --git a/output.tf b/output.tf new file mode 100644 index 0000000..d0ac432 --- /dev/null +++ b/output.tf @@ -0,0 +1,10 @@ +output "id" { + description = "ID to pass other modules in order to refer to this module as a dependency." + value = resource.null_resource.this.id +} + +output "admin_credentials" { + description = "Credentials for the administrator user of the master realm created on deployment." + value = data.kubernetes_secret.admin_credentials.data + sensitive = true +} diff --git a/outputs.tf b/outputs.tf deleted file mode 100644 index 6e48afa..0000000 --- a/outputs.tf +++ /dev/null @@ -1,20 +0,0 @@ -output "id" { - value = resource.null_resource.this.id -} - -output "oidc" { - description = "OIDC values" - sensitive = true - value = local.oidc -} - -output "keycloak_users" { - value = { for username, infos in local.user_map : username => lookup(infos, "password") } - sensitive = true -} - -#output "keycloak_admin_password" { -# description = "The password of Keycloak's admin user." -# value = data.kubernetes_secret.keycloak_admin_password.data.ADMIN_PASSWORD -# sensitive = true -#} diff --git a/terraform.tf b/terraform.tf new file mode 100644 index 0000000..c664117 --- /dev/null +++ b/terraform.tf @@ -0,0 +1,24 @@ +terraform { + required_providers { + argocd = { + source = "oboukili/argocd" + version = "~> 4" + } + utils = { + source = "cloudposse/utils" + version = "~> 1" + } + random = { + source = "random" + version = "~> 3" + } + null = { + source = "null" + version = "~> 3" + } + kubernetes = { + source = "kubernetes" + version = "~> 2" + } + } +} diff --git a/values.tmpl.yaml b/values.tmpl.yaml deleted file mode 100644 index 3ab9f59..0000000 --- a/values.tmpl.yaml +++ /dev/null @@ -1,37 +0,0 @@ -ingress: - enabled: true - annotations: - cert-manager.io/cluster-issuer: "${cluster_issuer}" - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.tls: "true" - ingress.kubernetes.io/ssl-redirect: "true" - kubernetes.io/ingress.allow-http: "false" - hosts: - - host: "${keycloak.domain}" - paths: - - / - - host: "keycloak.apps.${base_domain}" - paths: - - / - tls: - - secretName: keycloak-tls - hosts: - - "${keycloak.domain}" - - "keycloak.apps.${base_domain}" -service: - annotations: - traefik.ingress.kubernetes.io/service.serversscheme: https -keycloakClient: - client: - clientId: "${replace(oidc.client_id, "\"", "\\\"")}" - secret: "${replace(oidc.client_secret, "\"", "\\\"")}" - redirectUris: - - "*" -keycloakUsers: - %{ for username, infos in user_map } - ${username}: - name: ${lookup(infos, "name")} - first_name: ${lookup(infos, "first_name")} - password: ${lookup(infos, "password")} - email: ${lookup(infos, "email")} - %{ endfor } diff --git a/variables.tf b/variables.tf index 2ac6f4e..97143a1 100644 --- a/variables.tf +++ b/variables.tf @@ -3,18 +3,18 @@ ####################### variable "cluster_name" { - type = string + description = "Name given to the cluster. Value used for the ingress' URL of the application." + type = string } variable "base_domain" { - type = string + description = "Base domain of the cluster. Value used for the ingress' URL of the application." + type = string } -variable "argocd" { - type = object({ - namespace = string - domain = string - }) +variable "argocd_namespace" { + description = "Namespace used by Argo CD where the Application and AppProject resources should be created." + type = string } variable "target_revision" { @@ -24,33 +24,54 @@ variable "target_revision" { } variable "cluster_issuer" { - type = string - default = "ca-issuer" + description = "SSL certificate issuer to use. Usually you would configure this value as `letsencrypt-staging` or `letsencrypt-prod` on your root `*.tf` files." + type = string + default = "ca-issuer" } variable "namespace" { - type = string - default = "keycloak" + description = "Namespace where the applications's Kubernetes resources should be created. Namespace will be created in case it doesn't exist." + type = string + default = "keycloak" } +variable "helm_values" { + description = "Helm chart value overrides. They should be passed as a list of HCL structures." + type = any + default = [] +} -variable "extra_yaml" { - type = list(string) - default = [] +variable "app_autosync" { + description = "Automated sync options for the Argo CD Application resource." + type = object({ + allow_empty = optional(bool) + prune = optional(bool) + self_heal = optional(bool) + }) + default = { + allow_empty = false + prune = true + self_heal = true + } } variable "dependency_ids" { - type = map(string) - - default = {} + description = "IDs of the other modules on which this module depends on." + type = map(string) + default = {} } ####################### ## Module variables ####################### -variable "keycloak" { - description = "Keycloak settings" - type = any - default = {} +variable "database" { + description = "Keycloak external database server configuration." + type = object({ + vendor = string + host = string + username = string + password = string + }) + default = null } diff --git a/versions.tf b/versions.tf deleted file mode 100644 index fa8d146..0000000 --- a/versions.tf +++ /dev/null @@ -1,10 +0,0 @@ -terraform { - required_providers { - argocd = { - source = "oboukili/argocd" - } - utils = { - source = "cloudposse/utils" - } - } -}