From 34cb0b81e8cb4e7f8fdbe74c7e2d0c738f5636c8 Mon Sep 17 00:00:00 2001 From: adamstrawson Date: Thu, 28 Sep 2023 12:25:50 +0100 Subject: [PATCH 1/6] Support for Ory Network --- api/v1alpha1/oauth2client_types.go | 4 + api/v1alpha1/oauth2client_types_test.go | 1 + .../crd/bases/hydra.ory.sh_oauth2clients.yaml | 429 ++++++++---------- config/rbac/role.yaml | 64 +-- ...dra_v1alpha1_oauth2client_ory_network.yaml | 38 ++ controllers/oauth2client_controller.go | 2 + ...auth2client_controller_integration_test.go | 1 + hydra/client.go | 9 + main.go | 8 +- 9 files changed, 291 insertions(+), 265 deletions(-) create mode 100644 config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml diff --git a/api/v1alpha1/oauth2client_types.go b/api/v1alpha1/oauth2client_types.go index 57345964..2d9a7503 100644 --- a/api/v1alpha1/oauth2client_types.go +++ b/api/v1alpha1/oauth2client_types.go @@ -49,6 +49,10 @@ type HydraAdmin struct { // value "off" will force this to be off even if // `--forwarded-proto` is specified ForwardedProto string `json:"forwardedProto,omitempty"` + + // ApiKey overrides the `--api-key` flag. + // The Api Key is used to authenticate with Ory Network + ApiKey string `json:"apiKey,omitempty"` } // OAuth2ClientSpec defines the desired state of OAuth2Client diff --git a/api/v1alpha1/oauth2client_types_test.go b/api/v1alpha1/oauth2client_types_test.go index 0c33d004..cbce99c3 100644 --- a/api/v1alpha1/oauth2client_types_test.go +++ b/api/v1alpha1/oauth2client_types_test.go @@ -69,6 +69,7 @@ func TestCreateAPI(t *testing.T) { Port: 4445, // Endpoint: "/clients", ForwardedProto: "https", + ApiKey: "1234", } createErr = k8sClient.Create(context.TODO(), created) diff --git a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml index e896e947..422e3a8e 100644 --- a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml +++ b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml @@ -15,245 +15,214 @@ spec: singular: oauth2client scope: Namespaced versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: OAuth2Client is the Schema for the oauth2clients API - properties: - apiVersion: - description: - "APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the - latest internal value, and may reject unrecognized values. More - info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" - type: string - kind: - description: - "Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the - client submits requests to. Cannot be updated. In CamelCase. - More info: - https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" - type: string - metadata: - type: object - spec: - description: - OAuth2ClientSpec defines the desired state of OAuth2Client - properties: - allowedCorsOrigins: - description: - AllowedCorsOrigins is an array of allowed CORS origins - items: - description: - RedirectURI represents a redirect URI for the client - pattern: \w+:/?/?[^\s]+ - type: string - type: array - audience: - description: - Audience is a whitelist defining the audiences this client - is allowed to request tokens for - items: - type: string - type: array - clientName: - description: - ClientName is the human-readable string name of the client - to be presented to the end-user during authorization. + - name: v1alpha1 + schema: + openAPIV3Schema: + description: OAuth2Client is the Schema for the oauth2clients API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OAuth2ClientSpec defines the desired state of OAuth2Client + properties: + allowedCorsOrigins: + description: AllowedCorsOrigins is an array of allowed CORS origins + items: + description: RedirectURI represents a redirect URI for the client + pattern: \w+:/?/?[^\s]+ type: string - grantTypes: - description: - GrantTypes is an array of grant types the client is allowed - to use. - items: - description: GrantType represents an OAuth 2.0 grant type - enum: - - client_credentials - - authorization_code - - implicit - - refresh_token - type: string - maxItems: 4 - minItems: 1 - type: array - hydraAdmin: - description: - HydraAdmin is the optional configuration to use for managing - this client - properties: - endpoint: - description: - Endpoint is the endpoint for the hydra instance on which - to set up the client. This value will override the value - provided to `--endpoint` (defaults to `"/clients"` in - the application) - pattern: (^$|^/.*) - type: string - forwardedProto: - description: - ForwardedProto overrides the `--forwarded-proto` flag. - The value "off" will force this to be off even if - `--forwarded-proto` is specified - pattern: (^$|https?|off) - type: string - port: - description: - Port is the port for the hydra instance on which to set - up the client. This value will override the value - provided to `--hydra-port` - maximum: 65535 - type: integer - url: - description: - URL is the URL for the hydra instance on which to set up - the client. This value will override the value provided - to `--hydra-url` - maxLength: 64 - pattern: (^$|^https?://.*) - type: string - type: object - jwksUri: - description: - JwksUri Define the URL where the JSON Web Key Set should be - fetched from when performing the private_key_jwt client - authentication method. - pattern: (^$|^https?://.*) + type: array + audience: + description: Audience is a whitelist defining the audiences this client + is allowed to request tokens for + items: type: string - metadata: - description: Metadata is abritrary data - nullable: true - type: object - x-kubernetes-preserve-unknown-fields: true - postLogoutRedirectUris: - description: - PostLogoutRedirectURIs is an array of the post logout - redirect URIs allowed for the application - items: - description: - RedirectURI represents a redirect URI for the client - pattern: \w+:/?/?[^\s]+ + type: array + clientName: + description: ClientName is the human-readable string name of the client + to be presented to the end-user during authorization. + type: string + grantTypes: + description: GrantTypes is an array of grant types the client is allowed + to use. + items: + description: GrantType represents an OAuth 2.0 grant type + enum: + - client_credentials + - authorization_code + - implicit + - refresh_token + type: string + maxItems: 4 + minItems: 1 + type: array + hydraAdmin: + description: HydraAdmin is the optional configuration to use for managing + this client + properties: + apiKey: + description: ApiKey overrides the `--api-key` flag. The Api Key + is used to authenticate with Ory Network type: string - type: array - redirectUris: - description: - RedirectURIs is an array of the redirect URIs allowed for - the application - items: - description: - RedirectURI represents a redirect URI for the client - pattern: \w+:/?/?[^\s]+ + endpoint: + description: Endpoint is the endpoint for the hydra instance on + which to set up the client. This value will override the value + provided to `--endpoint` (defaults to `"/clients"` in the application) + pattern: (^$|^/.*) type: string - type: array - responseTypes: - description: - ResponseTypes is an array of the OAuth 2.0 response type - strings that the client can use at the authorization - endpoint. - items: - description: - ResponseType represents an OAuth 2.0 response type strings - enum: - - id_token - - code - - token - - code token - - code id_token - - id_token token - - code id_token token + forwardedProto: + description: ForwardedProto overrides the `--forwarded-proto` + flag. The value "off" will force this to be off even if `--forwarded-proto` + is specified + pattern: (^$|https?|off) type: string - maxItems: 3 - minItems: 1 - type: array - scope: - description: - Scope is a string containing a space-separated list of scope - values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) - that the client can use when requesting access tokens. - pattern: ([a-zA-Z0-9\.\*]+\s?)+ + port: + description: Port is the port for the hydra instance on which + to set up the client. This value will override the value provided + to `--hydra-port` + maximum: 65535 + type: integer + url: + description: URL is the URL for the hydra instance on which to + set up the client. This value will override the value provided + to `--hydra-url` + maxLength: 64 + pattern: (^$|^https?://.*) + type: string + type: object + jwksUri: + description: JwksUri Define the URL where the JSON Web Key Set should + be fetched from when performing the private_key_jwt client authentication + method. + pattern: (^$|^https?://.*) + type: string + metadata: + description: Metadata is abritrary data + nullable: true + type: object + x-kubernetes-preserve-unknown-fields: true + postLogoutRedirectUris: + description: PostLogoutRedirectURIs is an array of the post logout + redirect URIs allowed for the application + items: + description: RedirectURI represents a redirect URI for the client + pattern: \w+:/?/?[^\s]+ type: string - secretName: - description: - SecretName points to the K8s secret that contains this - client's ID and password - maxLength: 253 - minLength: 1 - pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' + type: array + redirectUris: + description: RedirectURIs is an array of the redirect URIs allowed + for the application + items: + description: RedirectURI represents a redirect URI for the client + pattern: \w+:/?/?[^\s]+ type: string - skipConsent: - default: false - description: - SkipConsent skips the consent screen for this client. - type: boolean - tokenEndpointAuthMethod: - allOf: - - enum: - - client_secret_basic - - client_secret_post - - private_key_jwt - - none - - enum: - - client_secret_basic - - client_secret_post - - private_key_jwt - - none - description: - Indication which authentication method shoud be used for the - token endpoint + type: array + responseTypes: + description: ResponseTypes is an array of the OAuth 2.0 response type + strings that the client can use at the authorization endpoint. + items: + description: ResponseType represents an OAuth 2.0 response type + strings + enum: + - id_token + - code + - token + - code token + - code id_token + - id_token token + - code id_token token type: string - required: - - grantTypes - - scope - - secretName - type: object - status: - description: - OAuth2ClientStatus defines the observed state of OAuth2Client - properties: - conditions: - items: - description: - OAuth2ClientCondition contains condition information for - an OAuth2Client - properties: - status: - enum: - - "True" - - "False" - - Unknown - type: string - type: - type: string - required: - - status - - type - type: object - type: array - observedGeneration: - description: - ObservedGeneration represents the most recent generation - observed by the daemon set controller. - format: int64 - type: integer - reconciliationError: - description: - ReconciliationError represents an error that occurred during - the reconciliation process + maxItems: 3 + minItems: 1 + type: array + scope: + description: Scope is a string containing a space-separated list of + scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) + that the client can use when requesting access tokens. + pattern: ([a-zA-Z0-9\.\*]+\s?)+ + type: string + secretName: + description: SecretName points to the K8s secret that contains this + client's ID and password + maxLength: 253 + minLength: 1 + pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' + type: string + skipConsent: + default: false + description: SkipConsent skips the consent screen for this client. + type: boolean + tokenEndpointAuthMethod: + allOf: + - enum: + - client_secret_basic + - client_secret_post + - private_key_jwt + - none + - enum: + - client_secret_basic + - client_secret_post + - private_key_jwt + - none + description: Indication which authentication method shoud be used + for the token endpoint + type: string + required: + - grantTypes + - scope + - secretName + type: object + status: + description: OAuth2ClientStatus defines the observed state of OAuth2Client + properties: + conditions: + items: + description: OAuth2ClientCondition contains condition information + for an OAuth2Client properties: - description: - description: - Description is the description of the reconciliation - error + status: + enum: + - "True" + - "False" + - Unknown type: string - statusCode: - description: - Code is the status code of the reconciliation error + type: type: string + required: + - status + - type type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} + type: array + observedGeneration: + description: ObservedGeneration represents the most recent generation + observed by the daemon set controller. + format: int64 + type: integer + reconciliationError: + description: ReconciliationError represents an error that occurred + during the reconciliation process + properties: + description: + description: Description is the description of the reconciliation + error + type: string + statusCode: + description: Code is the status code of the reconciliation error + type: string + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 9d43f10b..a38282dc 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -5,35 +5,35 @@ metadata: creationTimestamp: null name: manager-role rules: - - apiGroups: - - "" - resources: - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - - apiGroups: - - hydra.ory.sh - resources: - - oauth2clients - verbs: - - create - - delete - - get - - list - - patch - - update - - watch - - apiGroups: - - hydra.ory.sh - resources: - - oauth2clients/status - verbs: - - get - - patch - - update +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - hydra.ory.sh + resources: + - oauth2clients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - hydra.ory.sh + resources: + - oauth2clients/status + verbs: + - get + - patch + - update diff --git a/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml b/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml new file mode 100644 index 00000000..72f7358a --- /dev/null +++ b/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml @@ -0,0 +1,38 @@ +apiVersion: hydra.ory.sh/v1alpha1 +kind: OAuth2Client +metadata: + name: my-oauth2-client + namespace: default +spec: + grantTypes: + - client_credentials + - implicit + - authorization_code + - refresh_token + responseTypes: + - id_token + - code + - token + - code token + - code id_token + - id_token token + - code id_token token + scope: "read write" + secretName: my-secret-123 + # these are optional + redirectUris: + - https://client/account + - http://localhost:8080 + postLogoutRedirectUris: + - https://client/logout + audience: + - audience-a + - audience-b + hydraAdmin: + # if hydraAdmin is specified, all of these fields are requried, + # but they can be empty/0 + url: https://foobar.projects.oryapis.com + port: 4445 + endpoint: /clients + apiKey: 123456 + tokenEndpointAuthMethod: client_secret_basic diff --git a/controllers/oauth2client_controller.go b/controllers/oauth2client_controller.go index 36968fae..f883a95d 100644 --- a/controllers/oauth2client_controller.go +++ b/controllers/oauth2client_controller.go @@ -34,6 +34,7 @@ type clientKey struct { port int endpoint string forwardedProto string + apiKey string } // OAuth2ClientFactory is a function that creates oauth2 client. @@ -412,6 +413,7 @@ func (r *OAuth2ClientReconciler) getHydraClientForClient( port: spec.HydraAdmin.Port, endpoint: spec.HydraAdmin.Endpoint, forwardedProto: spec.HydraAdmin.ForwardedProto, + apiKey: spec.HydraAdmin.ApiKey, } r.mu.Lock() defer r.mu.Unlock() diff --git a/controllers/oauth2client_controller_integration_test.go b/controllers/oauth2client_controller_integration_test.go index 2d3d9f61..ea2489f1 100644 --- a/controllers/oauth2client_controller_integration_test.go +++ b/controllers/oauth2client_controller_integration_test.go @@ -494,6 +494,7 @@ func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client { Port: 4445, Endpoint: "/client", ForwardedProto: "https", + ApiKey: "1234", }, }} } diff --git a/hydra/client.go b/hydra/client.go index 1a48aa4b..8bbf0fcb 100644 --- a/hydra/client.go +++ b/hydra/client.go @@ -28,6 +28,7 @@ type InternalClient struct { HydraURL url.URL HTTPClient *http.Client ForwardedProto string + ApiKey string } // New returns a new hydra InternalClient instance. @@ -52,6 +53,10 @@ func New(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkip client.ForwardedProto = spec.HydraAdmin.ForwardedProto } + if spec.HydraAdmin.ApiKey != "" { + client.ApiKey = spec.HydraAdmin.ApiKey + } + return client, nil } @@ -186,6 +191,10 @@ func (c *InternalClient) newRequest(method, relativePath string, body interface{ req.Header.Add("X-Forwarded-Proto", c.ForwardedProto) } + if c.ApiKey != "" { + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.ApiKey)) + } + if body != nil { req.Header.Set("Content-Type", "application/json") } diff --git a/main.go b/main.go index fd181317..cbc8c596 100644 --- a/main.go +++ b/main.go @@ -35,9 +35,9 @@ func init() { func main() { var ( - metricsAddr, hydraURL, endpoint, forwardedProto, syncPeriod, tlsTrustStore, namespace, leaderElectorNs string - hydraPort int - enableLeaderElection, insecureSkipVerify bool + metricsAddr, hydraURL, endpoint, forwardedProto, syncPeriod, tlsTrustStore, namespace, leaderElectorNs, apiKey string + hydraPort int + enableLeaderElection, insecureSkipVerify bool ) flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") @@ -51,6 +51,7 @@ func main() { flag.BoolVar(&insecureSkipVerify, "insecure-skip-verify", false, "If set, http client will be configured to skip insecure verification to connect with hydra admin") flag.StringVar(&namespace, "namespace", "", "Namespace in which the controller should operate. Setting this will make the controller ignore other namespaces.") flag.StringVar(&leaderElectorNs, "leader-elector-namespace", "", "Leader elector namespace where controller should be set.") + flag.StringVar(&apiKey, "api-key", "", "If set, this adds the Ory Network API Key as the Authorization header in requests to the ORY Hydra admin server") flag.Parse() ctrl.SetLogger(zap.New(zap.UseDevMode(true))) @@ -85,6 +86,7 @@ func main() { Port: hydraPort, Endpoint: endpoint, ForwardedProto: forwardedProto, + ApiKey: apiKey, }, } if tlsTrustStore != "" { From ae844ca07bbe7c3d3484f7a7420d6c55bd33e40e Mon Sep 17 00:00:00 2001 From: Adam Strawson Date: Thu, 28 Sep 2023 12:31:25 +0100 Subject: [PATCH 2/6] Updated sample to include endpoint --- config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml b/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml index 72f7358a..5eb95e2d 100644 --- a/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml +++ b/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml @@ -32,7 +32,7 @@ spec: # if hydraAdmin is specified, all of these fields are requried, # but they can be empty/0 url: https://foobar.projects.oryapis.com - port: 4445 - endpoint: /clients + endpoint: /admin/clients + port: 443 apiKey: 123456 tokenEndpointAuthMethod: client_secret_basic From 3e5a3dfad93fc20d061a35da34daf470eff8b4a7 Mon Sep 17 00:00:00 2001 From: adamstrawson Date: Thu, 28 Sep 2023 12:34:59 +0100 Subject: [PATCH 3/6] Ran make format --- .../crd/bases/hydra.ory.sh_oauth2clients.yaml | 434 ++++++++++-------- config/rbac/role.yaml | 64 +-- 2 files changed, 267 insertions(+), 231 deletions(-) diff --git a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml index 422e3a8e..d4ff7a88 100644 --- a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml +++ b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml @@ -15,214 +15,250 @@ spec: singular: oauth2client scope: Namespaced versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: OAuth2Client is the Schema for the oauth2clients API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: OAuth2ClientSpec defines the desired state of OAuth2Client - properties: - allowedCorsOrigins: - description: AllowedCorsOrigins is an array of allowed CORS origins - items: - description: RedirectURI represents a redirect URI for the client - pattern: \w+:/?/?[^\s]+ - type: string - type: array - audience: - description: Audience is a whitelist defining the audiences this client - is allowed to request tokens for - items: - type: string - type: array - clientName: - description: ClientName is the human-readable string name of the client - to be presented to the end-user during authorization. - type: string - grantTypes: - description: GrantTypes is an array of grant types the client is allowed - to use. - items: - description: GrantType represents an OAuth 2.0 grant type - enum: - - client_credentials - - authorization_code - - implicit - - refresh_token + - name: v1alpha1 + schema: + openAPIV3Schema: + description: OAuth2Client is the Schema for the oauth2clients API + properties: + apiVersion: + description: + "APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the + latest internal value, and may reject unrecognized values. More + info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources" + type: string + kind: + description: + "Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the + client submits requests to. Cannot be updated. In CamelCase. + More info: + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds" + type: string + metadata: + type: object + spec: + description: + OAuth2ClientSpec defines the desired state of OAuth2Client + properties: + allowedCorsOrigins: + description: + AllowedCorsOrigins is an array of allowed CORS origins + items: + description: + RedirectURI represents a redirect URI for the client + pattern: \w+:/?/?[^\s]+ + type: string + type: array + audience: + description: + Audience is a whitelist defining the audiences this client + is allowed to request tokens for + items: + type: string + type: array + clientName: + description: + ClientName is the human-readable string name of the client + to be presented to the end-user during authorization. type: string - maxItems: 4 - minItems: 1 - type: array - hydraAdmin: - description: HydraAdmin is the optional configuration to use for managing - this client - properties: - apiKey: - description: ApiKey overrides the `--api-key` flag. The Api Key - is used to authenticate with Ory Network + grantTypes: + description: + GrantTypes is an array of grant types the client is allowed + to use. + items: + description: GrantType represents an OAuth 2.0 grant type + enum: + - client_credentials + - authorization_code + - implicit + - refresh_token type: string - endpoint: - description: Endpoint is the endpoint for the hydra instance on - which to set up the client. This value will override the value - provided to `--endpoint` (defaults to `"/clients"` in the application) - pattern: (^$|^/.*) + maxItems: 4 + minItems: 1 + type: array + hydraAdmin: + description: + HydraAdmin is the optional configuration to use for managing + this client + properties: + apiKey: + description: + ApiKey overrides the `--api-key` flag. The Api Key is + used to authenticate with Ory Network + type: string + endpoint: + description: + Endpoint is the endpoint for the hydra instance on which + to set up the client. This value will override the value + provided to `--endpoint` (defaults to `"/clients"` in + the application) + pattern: (^$|^/.*) + type: string + forwardedProto: + description: + ForwardedProto overrides the `--forwarded-proto` flag. + The value "off" will force this to be off even if + `--forwarded-proto` is specified + pattern: (^$|https?|off) + type: string + port: + description: + Port is the port for the hydra instance on which to set + up the client. This value will override the value + provided to `--hydra-port` + maximum: 65535 + type: integer + url: + description: + URL is the URL for the hydra instance on which to set up + the client. This value will override the value provided + to `--hydra-url` + maxLength: 64 + pattern: (^$|^https?://.*) + type: string + type: object + jwksUri: + description: + JwksUri Define the URL where the JSON Web Key Set should be + fetched from when performing the private_key_jwt client + authentication method. + pattern: (^$|^https?://.*) + type: string + metadata: + description: Metadata is abritrary data + nullable: true + type: object + x-kubernetes-preserve-unknown-fields: true + postLogoutRedirectUris: + description: + PostLogoutRedirectURIs is an array of the post logout + redirect URIs allowed for the application + items: + description: + RedirectURI represents a redirect URI for the client + pattern: \w+:/?/?[^\s]+ type: string - forwardedProto: - description: ForwardedProto overrides the `--forwarded-proto` - flag. The value "off" will force this to be off even if `--forwarded-proto` - is specified - pattern: (^$|https?|off) + type: array + redirectUris: + description: + RedirectURIs is an array of the redirect URIs allowed for + the application + items: + description: + RedirectURI represents a redirect URI for the client + pattern: \w+:/?/?[^\s]+ type: string - port: - description: Port is the port for the hydra instance on which - to set up the client. This value will override the value provided - to `--hydra-port` - maximum: 65535 - type: integer - url: - description: URL is the URL for the hydra instance on which to - set up the client. This value will override the value provided - to `--hydra-url` - maxLength: 64 - pattern: (^$|^https?://.*) + type: array + responseTypes: + description: + ResponseTypes is an array of the OAuth 2.0 response type + strings that the client can use at the authorization + endpoint. + items: + description: + ResponseType represents an OAuth 2.0 response type strings + enum: + - id_token + - code + - token + - code token + - code id_token + - id_token token + - code id_token token type: string - type: object - jwksUri: - description: JwksUri Define the URL where the JSON Web Key Set should - be fetched from when performing the private_key_jwt client authentication - method. - pattern: (^$|^https?://.*) - type: string - metadata: - description: Metadata is abritrary data - nullable: true - type: object - x-kubernetes-preserve-unknown-fields: true - postLogoutRedirectUris: - description: PostLogoutRedirectURIs is an array of the post logout - redirect URIs allowed for the application - items: - description: RedirectURI represents a redirect URI for the client - pattern: \w+:/?/?[^\s]+ + maxItems: 3 + minItems: 1 + type: array + scope: + description: + Scope is a string containing a space-separated list of scope + values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) + that the client can use when requesting access tokens. + pattern: ([a-zA-Z0-9\.\*]+\s?)+ type: string - type: array - redirectUris: - description: RedirectURIs is an array of the redirect URIs allowed - for the application - items: - description: RedirectURI represents a redirect URI for the client - pattern: \w+:/?/?[^\s]+ + secretName: + description: + SecretName points to the K8s secret that contains this + client's ID and password + maxLength: 253 + minLength: 1 + pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' type: string - type: array - responseTypes: - description: ResponseTypes is an array of the OAuth 2.0 response type - strings that the client can use at the authorization endpoint. - items: - description: ResponseType represents an OAuth 2.0 response type - strings - enum: - - id_token - - code - - token - - code token - - code id_token - - id_token token - - code id_token token + skipConsent: + default: false + description: + SkipConsent skips the consent screen for this client. + type: boolean + tokenEndpointAuthMethod: + allOf: + - enum: + - client_secret_basic + - client_secret_post + - private_key_jwt + - none + - enum: + - client_secret_basic + - client_secret_post + - private_key_jwt + - none + description: + Indication which authentication method shoud be used for the + token endpoint type: string - maxItems: 3 - minItems: 1 - type: array - scope: - description: Scope is a string containing a space-separated list of - scope values (as described in Section 3.3 of OAuth 2.0 [RFC6749]) - that the client can use when requesting access tokens. - pattern: ([a-zA-Z0-9\.\*]+\s?)+ - type: string - secretName: - description: SecretName points to the K8s secret that contains this - client's ID and password - maxLength: 253 - minLength: 1 - pattern: '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*' - type: string - skipConsent: - default: false - description: SkipConsent skips the consent screen for this client. - type: boolean - tokenEndpointAuthMethod: - allOf: - - enum: - - client_secret_basic - - client_secret_post - - private_key_jwt - - none - - enum: - - client_secret_basic - - client_secret_post - - private_key_jwt - - none - description: Indication which authentication method shoud be used - for the token endpoint - type: string - required: - - grantTypes - - scope - - secretName - type: object - status: - description: OAuth2ClientStatus defines the observed state of OAuth2Client - properties: - conditions: - items: - description: OAuth2ClientCondition contains condition information - for an OAuth2Client + required: + - grantTypes + - scope + - secretName + type: object + status: + description: + OAuth2ClientStatus defines the observed state of OAuth2Client + properties: + conditions: + items: + description: + OAuth2ClientCondition contains condition information for + an OAuth2Client + properties: + status: + enum: + - "True" + - "False" + - Unknown + type: string + type: + type: string + required: + - status + - type + type: object + type: array + observedGeneration: + description: + ObservedGeneration represents the most recent generation + observed by the daemon set controller. + format: int64 + type: integer + reconciliationError: + description: + ReconciliationError represents an error that occurred during + the reconciliation process properties: - status: - enum: - - "True" - - "False" - - Unknown + description: + description: + Description is the description of the reconciliation + error type: string - type: + statusCode: + description: + Code is the status code of the reconciliation error type: string - required: - - status - - type type: object - type: array - observedGeneration: - description: ObservedGeneration represents the most recent generation - observed by the daemon set controller. - format: int64 - type: integer - reconciliationError: - description: ReconciliationError represents an error that occurred - during the reconciliation process - properties: - description: - description: Description is the description of the reconciliation - error - type: string - statusCode: - description: Code is the status code of the reconciliation error - type: string - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index a38282dc..9d43f10b 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -5,35 +5,35 @@ metadata: creationTimestamp: null name: manager-role rules: -- apiGroups: - - "" - resources: - - secrets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - hydra.ory.sh - resources: - - oauth2clients - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - hydra.ory.sh - resources: - - oauth2clients/status - verbs: - - get - - patch - - update + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - hydra.ory.sh + resources: + - oauth2clients + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - hydra.ory.sh + resources: + - oauth2clients/status + verbs: + - get + - patch + - update From a9b944a341841fcae877c61daf4fd36887a22a6e Mon Sep 17 00:00:00 2001 From: adamstrawson Date: Thu, 28 Sep 2023 13:00:41 +0100 Subject: [PATCH 4/6] Update README with new flags --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b2c56cfe..4b403859 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ To deploy the controller, edit the value of the `--hydra-url` argument in the | ---------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- | ------------- | ---------------------------------------- | | **hydra-url** | yes | ORY Hydra's service address | - | ` ory-hydra-admin.ory.svc.cluster.local` | | **hydra-port** | no | ORY Hydra's service port | `4445` | `4445` | +| **endpoint** | no | ORY Hydra's client endpoint | `/clients` | `clients` | +| **api-key** | no | ORY Network API Key | `""` | `ory_pat_1234` | | **tls-trust-store** | no | TLS cert path for hydra client | `""` | `/etc/ssl/certs/ca-certificates.crt` | | **insecure-skip-verify** | no | Skip http client insecure verification | `false` | `true` or `false` | | **namespace** | no | Namespace in which the controller should operate. Setting this will make the controller ignore other namespaces. | `""` | `"my-namespace"` | From 51bcb9b8f84c8ffb118f0b423ee5c062c2a284e0 Mon Sep 17 00:00:00 2001 From: adamstrawson Date: Mon, 2 Oct 2023 10:08:52 +0100 Subject: [PATCH 5/6] Refactor to use Secret for API Keys --- api/v1alpha1/oauth2client_types.go | 16 +++++- api/v1alpha1/oauth2client_types_test.go | 1 - api/v1alpha1/zz_generated.deepcopy.go | 16 ++++++ .../crd/bases/hydra.ory.sh_oauth2clients.yaml | 20 +++++-- ...dra_v1alpha1_oauth2client_ory_network.yaml | 3 +- controllers/oauth2client_controller.go | 46 ++++++++++++++-- ...auth2client_controller_integration_test.go | 1 - hydra/client.go | 55 ++++++++++++++++++- main.go | 8 +-- 9 files changed, 143 insertions(+), 23 deletions(-) diff --git a/api/v1alpha1/oauth2client_types.go b/api/v1alpha1/oauth2client_types.go index 2d9a7503..235b8564 100644 --- a/api/v1alpha1/oauth2client_types.go +++ b/api/v1alpha1/oauth2client_types.go @@ -50,9 +50,19 @@ type HydraAdmin struct { // `--forwarded-proto` is specified ForwardedProto string `json:"forwardedProto,omitempty"` - // ApiKey overrides the `--api-key` flag. - // The Api Key is used to authenticate with Ory Network - ApiKey string `json:"apiKey,omitempty"` + // ApiKeySecretRef is an object to define the secret which contains + // Ory Network API Key + ApiKeySecretRef ApiKeySecretRef `json:"apiKeySecretRef,omitempty"` +} + +// ApiKeySecretRef contains Secret details for the API Key +type ApiKeySecretRef struct { + // Name of the secret containing the API Key + Name string `json:"name,omitempty"` + // Key of the secret for the API key + Key string `json:"key,omitempty"` + // Namespace of the secret if different from hydra-maester controller + Namespace string `json:"namespace,omitempty"` } // OAuth2ClientSpec defines the desired state of OAuth2Client diff --git a/api/v1alpha1/oauth2client_types_test.go b/api/v1alpha1/oauth2client_types_test.go index cbce99c3..0c33d004 100644 --- a/api/v1alpha1/oauth2client_types_test.go +++ b/api/v1alpha1/oauth2client_types_test.go @@ -69,7 +69,6 @@ func TestCreateAPI(t *testing.T) { Port: 4445, // Endpoint: "/clients", ForwardedProto: "https", - ApiKey: "1234", } createErr = k8sClient.Create(context.TODO(), created) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 18f8abf7..062e2fb4 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -26,9 +26,25 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApiKeySecretRef) DeepCopyInto(out *ApiKeySecretRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApiKeySecretRef. +func (in *ApiKeySecretRef) DeepCopy() *ApiKeySecretRef { + if in == nil { + return nil + } + out := new(ApiKeySecretRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HydraAdmin) DeepCopyInto(out *HydraAdmin) { *out = *in + out.ApiKeySecretRef = in.ApiKeySecretRef } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HydraAdmin. diff --git a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml index d4ff7a88..d5e5bf84 100644 --- a/config/crd/bases/hydra.ory.sh_oauth2clients.yaml +++ b/config/crd/bases/hydra.ory.sh_oauth2clients.yaml @@ -83,11 +83,23 @@ spec: HydraAdmin is the optional configuration to use for managing this client properties: - apiKey: + apiKeySecretRef: description: - ApiKey overrides the `--api-key` flag. The Api Key is - used to authenticate with Ory Network - type: string + ApiKeySecretRef is an object to define the secret which + contains Ory Network API Key + properties: + key: + description: Key of the secret for the API key + type: string + name: + description: Name of the secret containing the API Key + type: string + namespace: + description: + Namespace of the secret if different from + hydra-maester controller + type: string + type: object endpoint: description: Endpoint is the endpoint for the hydra instance on which diff --git a/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml b/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml index 5eb95e2d..0d511df4 100644 --- a/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml +++ b/config/samples/hydra_v1alpha1_oauth2client_ory_network.yaml @@ -34,5 +34,6 @@ spec: url: https://foobar.projects.oryapis.com endpoint: /admin/clients port: 443 - apiKey: 123456 + apiKeySecretRef: + name: ory_network_key tokenEndpointAuthMethod: client_secret_basic diff --git a/controllers/oauth2client_controller.go b/controllers/oauth2client_controller.go index f883a95d..7efec978 100644 --- a/controllers/oauth2client_controller.go +++ b/controllers/oauth2client_controller.go @@ -30,11 +30,16 @@ const ( ) type clientKey struct { - url string - port int - endpoint string - forwardedProto string - apiKey string + url string + port int + endpoint string + forwardedProto string + apiKeySecretRef ApiKeySecretRef +} + +type ApiKeySecretRef struct { + Name string + Namespace string } // OAuth2ClientFactory is a function that creates oauth2 client. @@ -413,8 +418,23 @@ func (r *OAuth2ClientReconciler) getHydraClientForClient( port: spec.HydraAdmin.Port, endpoint: spec.HydraAdmin.Endpoint, forwardedProto: spec.HydraAdmin.ForwardedProto, - apiKey: spec.HydraAdmin.ApiKey, } + + secretName := determineApiSecretName(&spec.HydraAdmin) + secretNamespace := determineApiSecretNamespace(&oauth2client) + + if secretName != "" && secretNamespace != "" { + key.apiKeySecretRef = ApiKeySecretRef{ + Name: secretName, + Namespace: secretNamespace, + } + spec.HydraAdmin.ApiKeySecretRef.Name = secretName + spec.HydraAdmin.ApiKeySecretRef.Namespace = secretNamespace + if spec.HydraAdmin.ApiKeySecretRef.Key == "" { + spec.HydraAdmin.ApiKeySecretRef.Key = "hydra_api_key" + } + } + r.mu.Lock() defer r.mu.Unlock() if c, ok := r.oauth2Clients[key]; ok { @@ -459,3 +479,17 @@ func removeString(slice []string, s string) (result []string) { } return } + +func determineApiSecretName(spec *hydrav1alpha1.HydraAdmin) string { + if spec.ApiKeySecretRef.Name != "" { + return spec.ApiKeySecretRef.Name + } + return "" +} + +func determineApiSecretNamespace(spec *hydrav1alpha1.OAuth2Client) string { + if spec.Spec.HydraAdmin.ApiKeySecretRef.Namespace != "" { + return spec.Spec.HydraAdmin.ApiKeySecretRef.Namespace + } + return spec.ObjectMeta.Namespace +} diff --git a/controllers/oauth2client_controller_integration_test.go b/controllers/oauth2client_controller_integration_test.go index ea2489f1..2d3d9f61 100644 --- a/controllers/oauth2client_controller_integration_test.go +++ b/controllers/oauth2client_controller_integration_test.go @@ -494,7 +494,6 @@ func testInstance(name, secretName string) *hydrav1alpha1.OAuth2Client { Port: 4445, Endpoint: "/client", ForwardedProto: "https", - ApiKey: "1234", }, }} } diff --git a/hydra/client.go b/hydra/client.go index 8bbf0fcb..2db45377 100644 --- a/hydra/client.go +++ b/hydra/client.go @@ -5,15 +5,22 @@ package hydra import ( "bytes" + "context" "encoding/json" "fmt" "io" "net/http" "net/url" + "os" "path" hydrav1alpha1 "github.com/ory/hydra-maester/api/v1alpha1" "github.com/ory/hydra-maester/helpers" + apiv1 "k8s.io/api/core/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) type Client interface { @@ -53,8 +60,16 @@ func New(spec hydrav1alpha1.OAuth2ClientSpec, tlsTrustStore string, insecureSkip client.ForwardedProto = spec.HydraAdmin.ForwardedProto } - if spec.HydraAdmin.ApiKey != "" { - client.ApiKey = spec.HydraAdmin.ApiKey + apiKey, err := fetchApiKey(spec.HydraAdmin.ApiKeySecretRef) + if err != nil { + return nil, err + } + + getEnv := os.Getenv("HYDRA_API_KEY") + if getEnv != "" { + client.ApiKey = getEnv + } else if apiKey != "" { + client.ApiKey = apiKey } return client, nil @@ -216,3 +231,39 @@ func (c *InternalClient) do(req *http.Request, v interface{}) (*http.Response, e } return resp, err } + +func fetchApiKey(spec hydrav1alpha1.ApiKeySecretRef) (string, error) { + + secretName := spec.Name + secretNamespace := spec.Namespace + + if secretName == "" || secretNamespace == "" { + return "", nil + } + + cfg := ctrl.GetConfigOrDie() + kubeClient, err := client.New(cfg, client.Options{}) + if err != nil { + ctrl.Log.Error(err, "unable to create client to fetch secret") + return "", err + } + + var secret apiv1.Secret + err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: secretNamespace}, &secret) + if err != nil { + if apierrs.IsNotFound(err) { + ctrl.Log.Error(err, fmt.Sprintf("secret %s/%s does not exist", secretName, secretNamespace)) + return "", fmt.Errorf("secret %s/%s does not exist", secretNamespace, secretName) + } + ctrl.Log.Error(err, fmt.Sprintf("error fetching secret %s/%s", secretName, secretNamespace)) + return "", fmt.Errorf("error fetching secret %s/%s: %s", secretNamespace, secretName, err) + } + + secretValue, ok := secret.Data[spec.Key] + if !ok { + ctrl.Log.Error(err, fmt.Sprintf("key %s doesn't exist within secret %s/%s", secretName, secretNamespace, spec.Key)) + return "", fmt.Errorf("key %s doesn't exist within secret %s/%s", secretName, secretNamespace, spec.Key) + } + + return string(secretValue), nil +} diff --git a/main.go b/main.go index cbc8c596..fd181317 100644 --- a/main.go +++ b/main.go @@ -35,9 +35,9 @@ func init() { func main() { var ( - metricsAddr, hydraURL, endpoint, forwardedProto, syncPeriod, tlsTrustStore, namespace, leaderElectorNs, apiKey string - hydraPort int - enableLeaderElection, insecureSkipVerify bool + metricsAddr, hydraURL, endpoint, forwardedProto, syncPeriod, tlsTrustStore, namespace, leaderElectorNs string + hydraPort int + enableLeaderElection, insecureSkipVerify bool ) flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") @@ -51,7 +51,6 @@ func main() { flag.BoolVar(&insecureSkipVerify, "insecure-skip-verify", false, "If set, http client will be configured to skip insecure verification to connect with hydra admin") flag.StringVar(&namespace, "namespace", "", "Namespace in which the controller should operate. Setting this will make the controller ignore other namespaces.") flag.StringVar(&leaderElectorNs, "leader-elector-namespace", "", "Leader elector namespace where controller should be set.") - flag.StringVar(&apiKey, "api-key", "", "If set, this adds the Ory Network API Key as the Authorization header in requests to the ORY Hydra admin server") flag.Parse() ctrl.SetLogger(zap.New(zap.UseDevMode(true))) @@ -86,7 +85,6 @@ func main() { Port: hydraPort, Endpoint: endpoint, ForwardedProto: forwardedProto, - ApiKey: apiKey, }, } if tlsTrustStore != "" { From 582c76b9046e389201cecda282221c8b7974c19a Mon Sep 17 00:00:00 2001 From: adamstrawson Date: Mon, 2 Oct 2023 10:16:59 +0100 Subject: [PATCH 6/6] Remove API flag from Readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4b403859..1a594447 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,6 @@ To deploy the controller, edit the value of the `--hydra-url` argument in the | **hydra-url** | yes | ORY Hydra's service address | - | ` ory-hydra-admin.ory.svc.cluster.local` | | **hydra-port** | no | ORY Hydra's service port | `4445` | `4445` | | **endpoint** | no | ORY Hydra's client endpoint | `/clients` | `clients` | -| **api-key** | no | ORY Network API Key | `""` | `ory_pat_1234` | | **tls-trust-store** | no | TLS cert path for hydra client | `""` | `/etc/ssl/certs/ca-certificates.crt` | | **insecure-skip-verify** | no | Skip http client insecure verification | `false` | `true` or `false` | | **namespace** | no | Namespace in which the controller should operate. Setting this will make the controller ignore other namespaces. | `""` | `"my-namespace"` |