Skip to content

Commit

Permalink
Use Shared CA (#12)
Browse files Browse the repository at this point in the history
Core now installs a shared CA to be be used by cluster microservices
making key distribution easier.  Use that rather than self-signing.
  • Loading branch information
spjmurray authored Jun 13, 2024
1 parent b57d3af commit 66df9d6
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 23 deletions.
4 changes: 2 additions & 2 deletions charts/region/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn's Region Controller

type: application

version: v0.1.5
appVersion: v0.1.5
version: v0.1.6
appVersion: v0.1.6

icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png
7 changes: 5 additions & 2 deletions charts/region/templates/region-controller/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ spec:
{{- end }}
{{- with $oidc := .Values.region.oidc }}
- --oidc-issuer={{ $oidc.issuer }}
{{- if $oidc.issuerCA }}
{{ printf "- --oidc-issuer-ca=%s" $oidc.issuerCA | nindent 8 }}
{{- if $oidc.issuerCASecretNamespace }}
{{ printf "- --oidc-issuer-ca-secret-namespace=%s" $oidc.issuerCASecretNamespace | nindent 8 }}
{{- end }}
{{- if $oidc.issuerCASecretName }}
{{ printf "- --oidc-issuer-ca-secret-name=%s" $oidc.issuerCASecretName | nindent 8 }}
{{- end }}
{{- end }}
{{- if .Values.region.otlpEndpoint }}
Expand Down
2 changes: 0 additions & 2 deletions charts/region/templates/region-controller/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ metadata:
cert-manager.io/cluster-issuer: {{ .Values.region.ingress.clusterIssuer | indent 2 }}
{{- else if .Values.region.ingress.issuer }}
cert-manager.io/issuer: {{ .Values.region.ingress.issuer }}
{{- else }}
cert-manager.io/issuer: unikorn-region-ingress
{{- end }}
{{- if .Values.region.ingress.externalDns }}
external-dns.alpha.kubernetes.io/hostname: {{ .Values.region.ingress.host }}
Expand Down
10 changes: 0 additions & 10 deletions charts/region/templates/region-controller/issuer.yaml

This file was deleted.

6 changes: 3 additions & 3 deletions charts/region/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ region:
issuer: ~

# clusterIssuer to use.
clusterIssuer: ~
clusterIssuer: unikorn-issuer

# If true, will add the external DNS hostname annotation.
externalDns: false
Expand All @@ -98,8 +98,8 @@ region:
issuer: https://identity.unikorn-cloud.org

# CA certificate to use to verify connections to the issuer, used in development only.
# This is a base64 encoded PEM file.
# issuerCA:
# issuerCASecretNamespace: ~
# issuerCASecretName: ~

# Sets the OTLP endpoint for shipping spans.
# otlpEndpoint: jaeger-collector.default:4318
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56
github.com/oapi-codegen/runtime v1.1.1
github.com/spf13/pflag v1.0.5
github.com/unikorn-cloud/core v0.1.46
github.com/unikorn-cloud/core v0.1.47
go.opentelemetry.io/otel v1.27.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0
go.opentelemetry.io/otel/sdk v1.27.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/unikorn-cloud/core v0.1.46 h1:bkRIMQt8zdAoPtXut5nngzqIweU9QpVyYm447KjE9ic=
github.com/unikorn-cloud/core v0.1.46/go.mod h1:cP39UQN7aSmsfjQuSMsworI4oBIwx4oA4u20CbPpfZw=
github.com/unikorn-cloud/core v0.1.47 h1:zZ3kWkGMan5v32jv3+BJ7YKYT08JwOMJmJQsn43nsTU=
github.com/unikorn-cloud/core v0.1.47/go.mod h1:cP39UQN7aSmsfjQuSMsworI4oBIwx4oA4u20CbPpfZw=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand Down
158 changes: 158 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
Copyright 2024 the Unikorn Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package client

import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/http"

"github.com/spf13/pflag"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"

"github.com/unikorn-cloud/core/pkg/authorization/accesstoken"
"github.com/unikorn-cloud/region/pkg/openapi"

corev1 "k8s.io/api/core/v1"

"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
// ErrFormatError is returned when a secret doesn't meet the specification.
ErrFormatError = errors.New("secret incorrectly formatted")
)

// Client wraps up the raw OpenAPI client with things to make it useable e.g.
// authorization and TLS.
type Client struct {
// client is a Kubenetes client.
client client.Client
// namespace is the namespace the client is running in.
namespace string
// host is the region host name.
host string
// caSecretNamespace tells us where to source the CA secret.
caSecretNamespace string
// caSecretName is the root CA secret of the region endpoint.
caSecretName string
}

// New creates a new client.
func New(client client.Client, namespace string) *Client {
return &Client{
client: client,
namespace: namespace,
}
}

// AddFlags adds the options to the CLI flags.
func (c *Client) AddFlags(f *pflag.FlagSet) {
f.StringVar(&c.host, "region-host", "", "Region endpoint URL.")
f.StringVar(&c.caSecretNamespace, "region-ca-secret-namespace", "", "Region endpoint CA certificate secret namespace.")
f.StringVar(&c.caSecretName, "region-ca-secret-name", "", "Region endpoint CA certificate secret.")
}

// tlsClientConfig abstracts away private TLS CAs or self signed certificates.
func (c *Client) tlsClientConfig(ctx context.Context) (*tls.Config, error) {
if c.caSecretName == "" {
//nolint:nilnil
return nil, nil
}

namespace := c.namespace

if c.caSecretNamespace != "" {
namespace = c.caSecretNamespace
}

secret := &corev1.Secret{}

if err := c.client.Get(ctx, client.ObjectKey{Namespace: namespace, Name: c.caSecretName}, secret); err != nil {
return nil, err
}

if secret.Type != corev1.SecretTypeTLS {
return nil, fmt.Errorf("%w: issuer CA not of type kubernetes.io/tls", ErrFormatError)
}

cert, ok := secret.Data[corev1.TLSCertKey]
if !ok {
return nil, fmt.Errorf("%w: issuer CA missing tls.crt", ErrFormatError)
}

certPool := x509.NewCertPool()

if ok := certPool.AppendCertsFromPEM(cert); !ok {
return nil, fmt.Errorf("%w: failed to load region CA certificate", ErrFormatError)
}

config := &tls.Config{
RootCAs: certPool,
MinVersion: tls.VersionTLS13,
}

return config, nil
}

// httpClient returns a new http client that will transparently do oauth2 header
// injection and refresh token updates.
func (c *Client) httpClient(ctx context.Context) (*http.Client, error) {
// Handle non-system CA certificates for the OIDC discovery protocol
// and oauth2 token refresh. This will return nil if none is specified
// and default to the system roots.
tlsClientConfig, err := c.tlsClientConfig(ctx)
if err != nil {
return nil, err
}

client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsClientConfig,
},
}

return client, nil
}

// accessTokenInjector implements OAuth2 bearer token authorization.
func accessTokenInjector(ctx context.Context, req *http.Request) error {
req.Header.Set("Authorization", "bearer "+accesstoken.FromContext(ctx))

otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))

return nil
}

// Client returns a new OpenAPI client that can be used to access the API.
func (c *Client) Client(ctx context.Context) (*openapi.ClientWithResponses, error) {
httpClient, err := c.httpClient(ctx)
if err != nil {
return nil, err
}

client, err := openapi.NewClientWithResponses(c.host, openapi.WithHTTPClient(httpClient), openapi.WithRequestEditorFn(accessTokenInjector))
if err != nil {
return nil, err
}

return client, nil
}
2 changes: 1 addition & 1 deletion pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (s *Server) GetServer(client client.Client) (*http.Server, error) {
router.MethodNotAllowed(http.HandlerFunc(handler.MethodNotAllowed))

// Setup middleware.
authorizer := oidc.NewAuthorizer(&s.AuthorizerOptions)
authorizer := oidc.NewAuthorizer(client, s.Options.Namespace, &s.AuthorizerOptions)

// Middleware specified here is applied to all requests post-routing.
// NOTE: these are applied in reverse order!!
Expand Down

0 comments on commit 66df9d6

Please sign in to comment.