diff --git a/deploy/crds/openliberty.io_openlibertyapplications_crd.yaml b/deploy/crds/openliberty.io_openlibertyapplications_crd.yaml index 8a42ee659..2822f8c00 100644 --- a/deploy/crds/openliberty.io_openlibertyapplications_crd.yaml +++ b/deploy/crds/openliberty.io_openlibertyapplications_crd.yaml @@ -5453,6 +5453,8 @@ spec: items: type: string type: array + routeAvailable: + type: boolean type: object version: v1beta1 versions: diff --git a/doc/user-guide.adoc b/doc/user-guide.adoc index f778bfa2c..75889f1d8 100644 --- a/doc/user-guide.adoc +++ b/doc/user-guide.adoc @@ -121,7 +121,7 @@ Each `OpenLibertyApplication` CR must specify `applicationImage` parameter. Spec | `route.certificateSecretRef` | A name of a secret that already contains TLS key, certificate and CA to be used in the route. Also can contain destination CA certificate. | `sso` | Specifies the configuration for single sign-on providers to authenticate with. Specify sensitive fields, such as _clientId_ and _clientSecret_, for the selected providers by using the `Secret`. For more information, see link:++#single-sign-on-sso++[Single Sign-On (SSO)]. | `sso.mapToUserRegistry` | Specifies whether to map a user identifier to a registry user. This parameter applies to all providers. -| `sso.redirectToRPHostAndPort` | Specifies a callback host and port number. This parameter applies to all providers. +| `sso.redirectToRPHostAndPort` | Specifies a callback protocol, host and port number, such as https://myfrontend.mycompany.com. This parameter applies to all providers. | `sso.github.hostname` | Specifies the host name of your enterprise GitHub, such as _github.mycompany.com_. The default is _github.com_, which is the public Github. | `sso.oidc` | The list of OpenID Connect (OIDC) providers to authenticate with. Required fields: _discoveryEndpoint_. Specify sensitive fields, such as _clientId_ and _clientSecret_, by using the `Secret`. | `sso.oidc[].discoveryEndpoint` | Specifies a discovery endpoint URL for the OpenID Connect provider. Required field. @@ -291,14 +291,14 @@ spec: - name: SEC_IMPORT_K8S_CERTS value: "true" sso: - redirectToRPHostAndPort: redirect-url.mycompany.com + redirectToRPHostAndPort: https://redirect-url.mycompany.com github: hostname: github.mycompany.com oauth2: - authorizationEndpoint: specify-required-value tokenEndpoint: specify-required-value oidc: - - discoveryEndpoint: specify-required-value + - discoveryEndpoint: specify-required-value service: certificate: isCA: true @@ -317,6 +317,52 @@ spec: termination: reencrypt ---- + +==== Using automatic registration with OIDC providers + +The operator can request a client Id and client Secret from providers, rather than requiring them in advance. This can simplify deployment, as the provider's administrator can supply the information needed for registration once, instead of supplying clientIds and secrets repetitively. Additional attributes named `-autoreg-` are added to the secret shown above. First the operator will make an https request to the `sso.oidc[].discoveryEndpoint` to obtain URLs for subsequent REST calls. Next it will make additional REST calls to the provider and obtain a client Id and client Secret. The secret will be updated with the obtained values. This is tested with Red Hat Single Sign-on (RHSSO). See the following example. + +[source,yaml] +---- +apiVersion: v1 +kind: Secret +metadata: + # Name of the secret should be in this format: -olapp-sso + name: my-app-olapp-sso + # Secret must be created in the same namespace as the OpenLibertyApplication instance + namespace: demo +type: Opaque +data: + # base64 encode the data before entering it here. + # + # Leave the clientId and secret out, registration will obtain them and update their values + # oidc-clientId + # oidc-clientSecret + # Reserved: -autoreg-registeredClientId and registeredClientSecret also contain these values. + # + # Automatic registration attributes have -autoreg- after the provider name + + # Red Hat Single Sign On requires an initial access token for registration + oidc-autoreg-initialAccessToken: xxxxxyyyyy + # + # IBM Security Verify requires a special clientId and clientSecret for registration. + # oidc-autoreg-clientId: bW9vb29vb28= + # oidc-autoreg-clientSecret: dGhlbGF1Z2hpbmdjb3c= + # Optional: Grant types are the types of OAuth flows the resulting clients will allow + # Default is authorization_code,refresh_token. Specify a comma separated list. + # oidc-autoreg-grantTypes: base64 data goes here + # + # Optional: Scopes limit the types of information about the user that the provider will return. + # Default is openid,profile. Specify a comma-separated list. + # oidc-autoreg-scopes: base64 data goes here + # + # Optional: To skip TLS certificate checking with the provider during registration, specify insecureTLS as true. Default is false. + # oidc-autoreg-insecureTLS: dHJ1ZQ== +---- + +Note: for RHSSO, optionally set the oidc parameter userNameAttribute to preferred_username to obtain the user ID that was used to log in. +For IBM Security Verify, set the attribute to given_name. + ==== Using multiple OIDC and OAuth 2.0 providers (Advanced) You can use multiple OIDC and OAuth 2.0 providers to authenticate with. First, configure and build application image with multiple OIDC and/or OAuth 2.0 providers. For example, set `ARG SEC_SSO_PROVIDERS="google oidc:provider1,provider2 oauth2:provider3,provider4"` in your Dockerfile. The provider name must be unique and must contain only alphanumeric characters. diff --git a/pkg/apis/openliberty/v1beta1/openlibertyapplication_types.go b/pkg/apis/openliberty/v1beta1/openlibertyapplication_types.go index d283e5f03..5a0e921c5 100644 --- a/pkg/apis/openliberty/v1beta1/openlibertyapplication_types.go +++ b/pkg/apis/openliberty/v1beta1/openlibertyapplication_types.go @@ -187,11 +187,12 @@ type OpenLibertyApplicationBindings struct { type OpenLibertyApplicationStatus struct { // +listType=map // +listMapKey=type - Conditions []StatusCondition `json:"conditions,omitempty"` - ConsumedServices common.ConsumedServices `json:"consumedServices,omitempty"` + Conditions []StatusCondition `json:"conditions,omitempty"` + ConsumedServices common.ConsumedServices `json:"consumedServices,omitempty"` + ImageReference string `json:"imageReference,omitempty"` + RouteAvailable *bool `json:"routeAvailable,omitempty"` // +listType=set - ResolvedBindings []string `json:"resolvedBindings,omitempty"` - ImageReference string `json:"imageReference,omitempty"` + ResolvedBindings []string `json:"resolvedBindings,omitempty"` } // StatusCondition ... diff --git a/pkg/apis/openliberty/v1beta1/zz_generated.deepcopy.go b/pkg/apis/openliberty/v1beta1/zz_generated.deepcopy.go index ca4bf47ce..634564d17 100644 --- a/pkg/apis/openliberty/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/openliberty/v1beta1/zz_generated.deepcopy.go @@ -665,6 +665,11 @@ func (in *OpenLibertyApplicationStatus) DeepCopyInto(out *OpenLibertyApplication (*out)[key] = outVal } } + if in.RouteAvailable != nil { + in, out := &in.RouteAvailable, &out.RouteAvailable + *out = new(bool) + **out = **in + } if in.ResolvedBindings != nil { in, out := &in.ResolvedBindings, &out.ResolvedBindings *out = make([]string, len(*in)) diff --git a/pkg/apis/openliberty/v1beta1/zz_generated.openapi.go b/pkg/apis/openliberty/v1beta1/zz_generated.openapi.go index fd0cb3157..55645f24b 100644 --- a/pkg/apis/openliberty/v1beta1/zz_generated.openapi.go +++ b/pkg/apis/openliberty/v1beta1/zz_generated.openapi.go @@ -11,31 +11,31 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.GithubLogin": schema_pkg_apis_openliberty_v1beta1_GithubLogin(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OAuth2Client": schema_pkg_apis_openliberty_v1beta1_OAuth2Client(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OidcClient": schema_pkg_apis_openliberty_v1beta1_OidcClient(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplication": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplication(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAffinity": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationAffinity(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAutoScaling": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationAutoScaling(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationMonitoring": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationMonitoring(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationRoute": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationRoute(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSSO": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSSO(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationService": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationService(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationServiceability": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationServiceability(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSpec": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStatus": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationStatus(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStorage": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationStorage(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDump": schema_pkg_apis_openliberty_v1beta1_OpenLibertyDump(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpSpec": schema_pkg_apis_openliberty_v1beta1_OpenLibertyDumpSpec(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpStatus": schema_pkg_apis_openliberty_v1beta1_OpenLibertyDumpStatus(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTrace": schema_pkg_apis_openliberty_v1beta1_OpenLibertyTrace(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceSpec": schema_pkg_apis_openliberty_v1beta1_OpenLibertyTraceSpec(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceStatus": schema_pkg_apis_openliberty_v1beta1_OpenLibertyTraceStatus(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperatedResource": schema_pkg_apis_openliberty_v1beta1_OperatedResource(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition": schema_pkg_apis_openliberty_v1beta1_OperationStatusCondition(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingConsumes": schema_pkg_apis_openliberty_v1beta1_ServiceBindingConsumes(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingProvides": schema_pkg_apis_openliberty_v1beta1_ServiceBindingProvides(ref), - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.StatusCondition": schema_pkg_apis_openliberty_v1beta1_StatusCondition(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.GithubLogin": schema_pkg_apis_openliberty_v1beta1_GithubLogin(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OAuth2Client": schema_pkg_apis_openliberty_v1beta1_OAuth2Client(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OidcClient": schema_pkg_apis_openliberty_v1beta1_OidcClient(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplication": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplication(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAffinity": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationAffinity(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAutoScaling": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationAutoScaling(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationMonitoring": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationMonitoring(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationRoute": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationRoute(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSSO": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSSO(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationService": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationService(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationServiceability": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationServiceability(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSpec": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStatus": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationStatus(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStorage": schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationStorage(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDump": schema_pkg_apis_openliberty_v1beta1_OpenLibertyDump(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpSpec": schema_pkg_apis_openliberty_v1beta1_OpenLibertyDumpSpec(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpStatus": schema_pkg_apis_openliberty_v1beta1_OpenLibertyDumpStatus(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTrace": schema_pkg_apis_openliberty_v1beta1_OpenLibertyTrace(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceSpec": schema_pkg_apis_openliberty_v1beta1_OpenLibertyTraceSpec(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceStatus": schema_pkg_apis_openliberty_v1beta1_OpenLibertyTraceStatus(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperatedResource": schema_pkg_apis_openliberty_v1beta1_OperatedResource(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition": schema_pkg_apis_openliberty_v1beta1_OperationStatusCondition(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingConsumes": schema_pkg_apis_openliberty_v1beta1_ServiceBindingConsumes(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingProvides": schema_pkg_apis_openliberty_v1beta1_ServiceBindingProvides(ref), + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.StatusCondition": schema_pkg_apis_openliberty_v1beta1_StatusCondition(ref), } } @@ -264,19 +264,19 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplication(ref common.Refer }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSpec"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStatus"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSpec", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSpec", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -453,7 +453,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationRoute(ref common. }, "certificate": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate"), }, }, "certificateSecretRef": { @@ -478,7 +478,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationRoute(ref common. }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate"}, } } @@ -500,7 +500,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSSO(ref common.Re Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OidcClient"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OidcClient"), }, }, }, @@ -517,7 +517,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSSO(ref common.Re Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OAuth2Client"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OAuth2Client"), }, }, }, @@ -525,7 +525,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSSO(ref common.Re }, "github": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.GithubLogin"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.GithubLogin"), }, }, "redirectToRPHostAndPort": { @@ -545,7 +545,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSSO(ref common.Re }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.GithubLogin", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OAuth2Client", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OidcClient"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.GithubLogin", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OAuth2Client", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OidcClient"}, } } @@ -623,7 +623,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationService(ref commo Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingConsumes"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingConsumes"), }, }, }, @@ -631,12 +631,12 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationService(ref commo }, "provides": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingProvides"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingProvides"), }, }, "certificate": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate"), }, }, "certificateSecretRef": { @@ -649,7 +649,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationService(ref commo }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingConsumes", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingProvides", "k8s.io/api/core/v1.ServicePort"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.Certificate", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingConsumes", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingProvides", "k8s.io/api/core/v1.ServicePort"}, } } @@ -705,7 +705,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref common.R }, "autoscaling": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAutoScaling"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAutoScaling"), }, }, "pullPolicy": { @@ -772,7 +772,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref common.R }, "service": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationService"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationService"), }, }, "expose": { @@ -842,7 +842,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref common.R }, "storage": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStorage"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStorage"), }, }, "createKnativeService": { @@ -853,7 +853,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref common.R }, "monitoring": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationMonitoring"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationMonitoring"), }, }, "createAppDefinition": { @@ -906,27 +906,27 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref common.R }, "route": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationRoute"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationRoute"), }, }, "bindings": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationBindings"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationBindings"), }, }, "affinity": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAffinity"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAffinity"), }, }, "serviceability": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationServiceability"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationServiceability"), }, }, "sso": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSSO"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSSO"), }, }, }, @@ -934,7 +934,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationSpec(ref common.R }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAffinity", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAutoScaling", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationBindings", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationMonitoring", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationRoute", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSSO", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationService", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationServiceability", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStorage", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAffinity", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationAutoScaling", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationBindings", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationMonitoring", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationRoute", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationSSO", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationService", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationServiceability", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyApplicationStorage", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount"}, } } @@ -957,7 +957,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationStatus(ref common Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.StatusCondition"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.StatusCondition"), }, }, }, @@ -984,6 +984,18 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationStatus(ref common }, }, }, + "imageReference": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "routeAvailable": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, "resolvedBindings": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -1002,17 +1014,11 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyApplicationStatus(ref common }, }, }, - "imageReference": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, }, }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.StatusCondition"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.StatusCondition"}, } } @@ -1076,19 +1082,19 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyDump(ref common.ReferenceCal }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpSpec"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpStatus"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpSpec", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpSpec", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyDumpStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -1148,7 +1154,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyDumpStatus(ref common.Refere Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"), }, }, }, @@ -1164,7 +1170,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyDumpStatus(ref common.Refere }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"}, } } @@ -1196,19 +1202,19 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyTrace(ref common.ReferenceCa }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceSpec"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceStatus"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceSpec", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceSpec", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OpenLibertyTraceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -1274,7 +1280,7 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyTraceStatus(ref common.Refer Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"), }, }, }, @@ -1282,14 +1288,14 @@ func schema_pkg_apis_openliberty_v1beta1_OpenLibertyTraceStatus(ref common.Refer }, "operatedResource": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperatedResource"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperatedResource"), }, }, }, }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperatedResource", "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperatedResource", "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.OperationStatusCondition"}, } } @@ -1432,7 +1438,7 @@ func schema_pkg_apis_openliberty_v1beta1_ServiceBindingProvides(ref common.Refer }, "auth": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingAuth"), + Ref: ref("github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingAuth"), }, }, }, @@ -1440,7 +1446,7 @@ func schema_pkg_apis_openliberty_v1beta1_ServiceBindingProvides(ref common.Refer }, }, Dependencies: []string{ - "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingAuth"}, + "github.com/open-liberty-operator/pkg/apis/openliberty/v1beta1.ServiceBindingAuth"}, } } diff --git a/pkg/controller/openliberty/openlibertyapplication_controller.go b/pkg/controller/openliberty/openlibertyapplication_controller.go index 73ebb1beb..0c46c43b0 100644 --- a/pkg/controller/openliberty/openlibertyapplication_controller.go +++ b/pkg/controller/openliberty/openlibertyapplication_controller.go @@ -340,8 +340,7 @@ type ReconcileOpenLiberty struct { // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. func (r *ReconcileOpenLiberty) Reconcile(request reconcile.Request) (reconcile.Result, error) { reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - reqLogger.Info("Reconciling OpenLibertyApplication") - + reqLogger.Info("Reconcile OpenLibertyApplication - starting") ns, err := k8sutil.GetOperatorNamespace() // When running the operator locally, `ns` will be empty string if ns == "" { @@ -649,13 +648,11 @@ func (r *ReconcileOpenLiberty) Reconcile(request reconcile.Request) (reconcile.R oputils.CustomizeServiceBinding(resolvedBindingSecret, &statefulSet.Spec.Template.Spec, instance) lutils.CustomizeLibertyEnv(&statefulSet.Spec.Template, instance) if instance.Spec.SSO != nil { - secretName := instance.GetName() + ssoSecretNameSuffix - ssoSecret := &corev1.Secret{} - err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: instance.GetNamespace()}, ssoSecret) + err = lutils.CustomizeEnvSSO(&statefulSet.Spec.Template, instance, r.GetClient(), r.IsOpenShift()) if err != nil { - return errors.Wrapf(err, "Secret for Single sign-on (SSO) was not found. Create a secret named %q in namespace %q with the credentials for the login providers you selected in application image.", secretName, instance.GetNamespace()) + reqLogger.Error(err, "Failed to reconcile Single sign-on configuration") + return err } - lutils.CustomizeEnvSSO(&statefulSet.Spec.Template, instance, ssoSecret) } lutils.ConfigureServiceability(&statefulSet.Spec.Template, instance) if joiningExistingApplication || instance.Spec.CreateAppDefinition == nil || *instance.Spec.CreateAppDefinition { @@ -668,7 +665,7 @@ func (r *ReconcileOpenLiberty) Reconcile(request reconcile.Request) (reconcile.R reqLogger.Error(err, "Failed to reconcile StatefulSet") return r.ManageError(err, common.StatusConditionTypeReconciled, instance) } - + } else { // Delete StatefulSet if exists statefulSet := &appsv1.StatefulSet{ObjectMeta: defaultMeta} @@ -692,14 +689,12 @@ func (r *ReconcileOpenLiberty) Reconcile(request reconcile.Request) (reconcile.R oputils.CustomizePodSpec(&deploy.Spec.Template, instance) oputils.CustomizeServiceBinding(resolvedBindingSecret, &deploy.Spec.Template.Spec, instance) lutils.CustomizeLibertyEnv(&deploy.Spec.Template, instance) - if instance.Spec.SSO != nil { - secretName := instance.GetName() + ssoSecretNameSuffix - ssoSecret := &corev1.Secret{} - err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: instance.GetNamespace()}, ssoSecret) + if instance.Spec.SSO != nil { + err = lutils.CustomizeEnvSSO(&deploy.Spec.Template, instance, r.GetClient(), r.IsOpenShift()) if err != nil { - return errors.Wrapf(err, "Secret for Single sign-on (SSO) was not found. Create a secret named %q in namespace %q with the credentials for the login providers you selected in application image.", secretName, instance.GetNamespace()) + reqLogger.Error(err, "Failed to reconcile Single sign-on configuration") + return err } - lutils.CustomizeEnvSSO(&deploy.Spec.Template, instance, ssoSecret) } lutils.ConfigureServiceability(&deploy.Spec.Template, instance) @@ -755,6 +750,7 @@ func (r *ReconcileOpenLiberty) Reconcile(request reconcile.Request) (reconcile.R reqLogger.Error(err, "Failed to reconcile Route") return r.ManageError(err, common.StatusConditionTypeReconciled, instance) } + } else { route := &routev1.Route{ObjectMeta: defaultMeta} err = r.DeleteResource(route) @@ -817,6 +813,7 @@ func (r *ReconcileOpenLiberty) Reconcile(request reconcile.Request) (reconcile.R reqLogger.V(1).Info(fmt.Sprintf("%s is not supported", prometheusv1.SchemeGroupVersion.String())) } + reqLogger.Info("Reconcile OpenLibertyApplication - completed") return r.ManageSuccess(common.StatusConditionTypeReconciled, instance) } diff --git a/pkg/utils/register.go b/pkg/utils/register.go new file mode 100644 index 000000000..f64bb8d06 --- /dev/null +++ b/pkg/utils/register.go @@ -0,0 +1,299 @@ +package utils + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + gherrors "github.com/pkg/errors" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" +) + +type RegisterData struct { + DiscoveryURL string + RouteURL string + RedirectToRPHostAndPort string + ProviderId string + Scopes string + GrantTypes string + InitialAccessToken string + InitialClientId string + InitialClientSecret string + RegistrationURL string + InsecureTLS bool +} + +func RegisterWithOidcProvider(regData RegisterData) (string, string, error) { + return doRegister(regData) +} + +// register with oidc provider and create a new client. return the new client id and client secret, or an error. +func doRegister(rdata RegisterData) (string, string, error) { + // process: + // 1) call the provider's discovery endpoint to find the token and registration urls. + // 2) If we do not have an initial access token, + // 2.5) Use supplied clientId and secret in a Client Credentials grant to obtain an access token. + // 3) Use the access token to register and obtain a new client id and secret. + + registrationURL, tokenURL, err := getURLs(rdata.DiscoveryURL, rdata.InsecureTLS, rdata.ProviderId) + if err != nil { + return "", "", err + } + if tokenURL == "" { + return "", "", gherrors.New("Provider " + rdata.ProviderId + ": failed to obtain token endpoint from discovery endpoint.") + } + + // ICI: if we don't have initial token, use client and secret to go get one. + var token = rdata.InitialAccessToken + if token == "" && (rdata.InitialClientId == "" || rdata.InitialClientSecret == "") { + return "", "", gherrors.New("Provider " + rdata.ProviderId + ": registration data for Single sign-on (SSO) is missing required fields," + + " one or more of initialAccessToken, initialClientId, or initialClientSecret.") + } + if token == "" { + rtoken, err := requestAccessToken(rdata, tokenURL) + if err != nil { + return "", "", err + } + if rtoken == "" { + return "", "", gherrors.New("Provider " + rdata.ProviderId + ": failed to obtain access token for registration.") + } + rdata.InitialAccessToken = rtoken + } + + if rdata.RegistrationURL != "" { + registrationURL = rdata.RegistrationURL + } + // registrationURL should be in discovery data but allow it to be supplied manually if not. + if registrationURL == "" { + return "", "", gherrors.New("Provider " + rdata.ProviderId + ": failed to obtain registration URL - specify registrationURL in registration data secret.") + } + + registrationRequestJson := buildRegistrationRequestJson(rdata) + + registrationResponse, err := sendHTTPRequest(registrationRequestJson, registrationURL, "POST", "", rdata.InitialAccessToken, rdata.InsecureTLS, rdata.ProviderId) + if err != nil { + return "", "", err + } + + // extract id and secret from body + id, secret, err := parseRegistrationResponseJson(registrationResponse, rdata.ProviderId) + if err != nil { + return "", "", err + } + return id, secret, nil +} + +func requestAccessToken(rdata RegisterData, tokenURL string) (string, error) { + tokenRequestContent := "grant_type=client_credentials&scope=" + getScopes(rdata) + tokenResponse, err := sendHTTPRequest(tokenRequestContent, tokenURL, "POST", rdata.InitialClientId, rdata.InitialClientSecret, rdata.InsecureTLS, rdata.ProviderId) + if err != nil { + return "", err + } + token, err := parseTokenResponse(tokenResponse, rdata.ProviderId) + if err != nil { + return "", err + } + return token, nil + +} + +// parse token response and return token +func parseTokenResponse(respJson string, providerId string) (string, error) { + type token struct { + Access_token string + } + var cdata token + err := json.Unmarshal([]byte(respJson), &cdata) + if err != nil { + return "", errors.New("Provider " + providerId + ": error parsing token response: " + err.Error() + " Data: " + respJson) + } + return cdata.Access_token, nil +} + +// parse the response and return the client id and client secret +func parseRegistrationResponseJson(respJson string, providerId string) (string, string, error) { + type idsecret struct { + Client_id string + Client_secret string + } + + var cdata idsecret + err := json.Unmarshal([]byte(respJson), &cdata) + if err != nil { + return "", "", errors.New("Provider " + providerId + ": error parsing registration response: " + err.Error() + " Data: " + respJson) + } + return cdata.Client_id, cdata.Client_secret, nil +} + +// build the JSON for the client registration request. Form the redirectURL from the route URL. +func buildRegistrationRequestJson(rdata RegisterData) string { + now := time.Now() + sysClockMillisec := now.UnixNano() / 1000000 + // rhsso will not accept a supplied value for client_id, so leave a comment in the name + clientName := "LibertyOperator-" + strings.Replace(rdata.RouteURL, "https://", "", 1) + "-" + + strconv.FormatInt(sysClockMillisec, 10) + + // IBM Security Verify needs some special things in the request. + isvAttribs := "" + if rdata.InitialClientId != "" { + isvAttribs = "\"enforce_pkce\":false," + + "\"all_users_entitled\":true," + + "\"consent_action\":\"never_prompt\"," + } + + return "{" + isvAttribs + + "\"client_name\":\"" + clientName + "\"," + + "\"grant_types\":[" + getGrantTypes(rdata) + "]," + + "\"scope\":\"" + getScopes(rdata) + "\"," + + "\"redirect_uris\":[\"" + getRedirectUri(rdata) + "\"]}" +} + +func getScopes(rdata RegisterData) string { + if rdata.Scopes == "" { + return "openid profile" + } + + var result = "" + gts := strings.Split(rdata.Scopes, ",") + for _, gt := range gts { + result += strings.Trim(gt, " ") + " " + } + return strings.TrimSuffix(result, " ") +} + +func getGrantTypes(rdata RegisterData) string { + if rdata.GrantTypes == "" { + return "\"authorization_code\",\"refresh_token\"" + } + + var result = "" + gts := strings.Split(rdata.GrantTypes, ",") + for _, gt := range gts { + result += "\"" + strings.Trim(gt, " ") + "\"" + "," + } + return strings.TrimSuffix(result, ",") +} + +func getRedirectUri(rdata RegisterData) string { + providerId := rdata.ProviderId + if providerId == "" { + providerId = "oidc" + } + suffix := "/ibm/api/social-login/redirect/" + providerId + if rdata.RedirectToRPHostAndPort != "" { + return rdata.RedirectToRPHostAndPort + suffix + } + return rdata.RouteURL + suffix +} + +// retrieve the registration and token URLs from the provider's discovery URL. +// return an error if we don't get back two valid url's. +// todo: more error checking needed to make that true? +func getURLs(discoveryURL string, insecureTLS bool, providerId string) (string, string, error) { + discoveryResult, err := sendHTTPRequest("", discoveryURL, "GET", "", "", insecureTLS, providerId) + if err != nil { + return "", "", err + } + + type regEp struct { + Registration_endpoint string + } + + type tokenEp struct { + Token_endpoint string + } + + var regdata regEp + var tokendata tokenEp + err = json.Unmarshal([]byte(discoveryResult), ®data) + if err != nil { + return "", "", errors.New("Provider " + providerId + ": error unmarshalling data from discovery endpoint: " + err.Error() + " Data: " + discoveryResult) + } + err = json.Unmarshal([]byte(discoveryResult), &tokendata) + if err != nil { + return "", "", errors.New("Provider " + providerId + ": error unmarshalling data from discovery endpoint: " + err.Error() + " Data: " + discoveryResult) + } + + return regdata.Registration_endpoint, tokendata.Token_endpoint, nil +} + +// Send an http(s) request. return response body and error. +// content to send can be an empty string. Json will be detected. Method should be GET or POST. +// if id is set, send id and passwordOrToken as basic auth header, otherwise send token as bearer auth header. +// If error occurs, body will be "error". +func sendHTTPRequest(content string, URL string, method string, id string, passwordOrToken string, insecureTLS bool, providerId string) (string, error) { + + rootCAPool, _ := x509.SystemCertPool() + if rootCAPool == nil { + rootCAPool = x509.NewCertPool() + } + + if !insecureTLS { + cert, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt") + if err != nil { + return "", errors.New("Error reading TLS certificates: " + err.Error()) + } + rootCAPool.AppendCertsFromPEM(cert) + } + + client := &http.Client{ + Timeout: time.Second * 20, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: rootCAPool, + InsecureSkipVerify: insecureTLS, + }, + }, + } + + var requestBody = []byte(content) + + request, err := http.NewRequest(method, URL, bytes.NewBuffer(requestBody)) + if strings.HasPrefix(content, "{") { + request.Header.Set("Content-type", "application/json") + request.Header.Set("Accept", "application/json") + } else { + request.Header.Set("Content-type", "application/x-www-form-urlencoded") + } + + if id != "" { + request.SetBasicAuth(id, passwordOrToken) + } else { + if passwordOrToken != "" { + request.Header.Set("Authorization", "Bearer "+passwordOrToken) + } + } + + const errorStr = "error" + var errorMsgPreamble = "Provider " + providerId + ": error occurred communicating with OIDC provider. URL: " + URL + ": " + if err != nil { + return errorStr, errors.New(errorMsgPreamble + err.Error()) + } + + response, err := client.Do(request) + if response == nil { + return errorStr, errors.New(errorMsgPreamble + err.Error()) // bad hostname, can't connect, etc. + } + defer response.Body.Close() + + if err != nil { + return errorStr, errors.New(errorMsgPreamble + err.Error()) // timeout, conn reset, etc. + } + + respBytes, err := ioutil.ReadAll(response.Body) + if err != nil { + return errorStr, errors.New(errorMsgPreamble + err.Error()) + } + respString := string(respBytes) + + // a successful registration usually has a 201 response code. + if response.StatusCode != 200 && response.StatusCode != 201 { + return errorStr, errors.New(errorMsgPreamble + response.Status + ". " + respString + ". Data sent was: " + content) + } + return respString, nil +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 86c378cd0..46edde393 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -2,20 +2,25 @@ package utils import ( "bytes" + "context" "fmt" "sort" "strconv" "strings" openlibertyv1beta1 "github.com/OpenLiberty/open-liberty-operator/pkg/apis/openliberty/v1beta1" - + routev1 "github.com/openshift/api/route/v1" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" ) @@ -218,15 +223,28 @@ func createEnvVarSSO(loginID string, envSuffix string, value interface{}) *corev } // CustomizeEnvSSO Process the configuration for SSO login providers -func CustomizeEnvSSO(pts *corev1.PodTemplateSpec, instance *openlibertyv1beta1.OpenLibertyApplication, ssoSecret *corev1.Secret) { +func CustomizeEnvSSO(pts *corev1.PodTemplateSpec, instance *openlibertyv1beta1.OpenLibertyApplication, client client.Client, isOpenShift bool) error { + const ssoSecretNameSuffix = "-olapp-sso" + const autoregFragment = "-autoreg-" + secretName := instance.GetName() + ssoSecretNameSuffix + ssoSecret := &corev1.Secret{} + err := client.Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: instance.GetNamespace()}, ssoSecret) + if err != nil { + return errors.Wrapf(err, "Secret for Single sign-on (SSO) was not found. Create a secret named %q in namespace %q with the credentials for the login providers you selected in application image.", secretName, instance.GetNamespace()) + } + + ssoEnv := []corev1.EnvVar{} var secretKeys []string - for k := range ssoSecret.Data { + for k := range ssoSecret.Data { //ranging over a map returns it's keys. + if strings.Contains(k, autoregFragment) { // skip -autoreg- + continue + } secretKeys = append(secretKeys, k) } sort.Strings(secretKeys) - ssoEnv := []corev1.EnvVar{} + // append all the values in the secret into the env vars. for _, k := range secretKeys { ssoEnv = append(ssoEnv, corev1.EnvVar{ Name: ssoEnvVarPrefix + normalizeEnvVariableName(k), @@ -241,6 +259,7 @@ func CustomizeEnvSSO(pts *corev1.PodTemplateSpec, instance *openlibertyv1beta1.O }) } + // append all the values in the spec into the env vars. sso := instance.Spec.SSO if sso.MapToUserRegistry != nil { ssoEnv = append(ssoEnv, *createEnvVarSSO("", "MAPTOUSERREGISTRY", *sso.MapToUserRegistry)) @@ -254,6 +273,7 @@ func CustomizeEnvSSO(pts *corev1.PodTemplateSpec, instance *openlibertyv1beta1.O ssoEnv = append(ssoEnv, *createEnvVarSSO("", "GITHUB_HOSTNAME", sso.Github.Hostname)) } + ssoSecretUpdates := make(map[string][]byte) for _, oidcClient := range sso.OIDC { id := strings.ToUpper(oidcClient.ID) if id == "" { @@ -286,6 +306,69 @@ func CustomizeEnvSSO(pts *corev1.PodTemplateSpec, instance *openlibertyv1beta1.O if oidcClient.HostNameVerificationEnabled != nil { ssoEnv = append(ssoEnv, *createEnvVarSSO(id, "_HOSTNAMEVERIFICATIONENABLED", *oidcClient.HostNameVerificationEnabled)) } + // if no clientId specified for this provider, try auto-registration + clientName := oidcClient.ID + if clientName == "" { + clientName = "oidc" + } + clientId := string(ssoSecret.Data[clientName + "-clientId"]) + clientSecret := string(ssoSecret.Data[clientName + "-clientSecret"]) + + if isOpenShift && clientId == "" { + theRoute := &routev1.Route{} + err = client.Get(context.TODO(), types.NamespacedName{Name: instance.GetName(), Namespace: instance.GetNamespace()}, theRoute) + if err != nil { + // if route is unavailable, we want to let reconciliation proceed so it will be created. + // Update status of the instance so reconcilation will be triggered again. + b := false + instance.Status.RouteAvailable = &b + logf.Log.WithName("utils").Info("CustomizeEnvSSO waiting for route to become available for provider " + clientName + ", requeue") + return nil + } + + // route available, we don't have a client id and secret yet, go get one + prefix := strings.ToLower(id) + autoregFragment + buf := string(ssoSecret.Data[prefix+"insecureTLS"]) + insecure := buf == "true" || buf == "TRUE" + regData := RegisterData{ + DiscoveryURL: oidcClient.DiscoveryEndpoint, + RouteURL: "https://" + theRoute.Spec.Host, + RedirectToRPHostAndPort: sso.RedirectToRPHostAndPort, + InitialAccessToken: string(ssoSecret.Data[prefix+"initialAccessToken"]), + InitialClientId: string(ssoSecret.Data[prefix+"initialClientId"]), + InitialClientSecret: string(ssoSecret.Data[prefix+"initialClientSecret"]), + GrantTypes: string(ssoSecret.Data[prefix+"grantTypes"]), + Scopes: string(ssoSecret.Data[prefix+"scopes"]), + InsecureTLS: insecure, + ProviderId: clientName, + } + + clientId, clientSecret, err = RegisterWithOidcProvider(regData) + if err != nil { + return errors.Wrapf(err, "Error occured during registration with OIDC for provider " + clientName) + } + + ssoSecretUpdates[clientName + autoregFragment + "RegisteredOidcClientId"] = []byte(clientId) + ssoSecretUpdates[clientName + autoregFragment + "RegisteredOidcSecret"] = []byte(clientSecret) + ssoSecretUpdates[clientName + "-clientId"] = []byte(clientId) + ssoSecretUpdates[clientName + "-clientSecret"] = []byte(clientSecret) + + b := true + instance.Status.RouteAvailable = &b + } // end auto-reg + } // end for + + if len(ssoSecretUpdates) > 0 { // performant: do all the secret udpates at once + _, err = controllerutil.CreateOrUpdate(context.TODO(), client, ssoSecret, func() error { + for key, value := range ssoSecretUpdates { + ssoSecret.Data[key] = value + } + return nil + }) + + if err != nil { + return errors.Wrapf(err, "Error occured when updating SSO secret") + } } for _, oauth2Client := range sso.Oauth2 { @@ -348,4 +431,5 @@ func CustomizeEnvSSO(pts *corev1.PodTemplateSpec, instance *openlibertyv1beta1.O pts.Spec.Containers[0].Env = append(pts.Spec.Containers[0].Env, v) } } + return nil }