Skip to content

Commit

Permalink
feat: add ClusterOriginIssuer
Browse files Browse the repository at this point in the history
When using this controller in single-tenant clusters it becomes
desirable for many users to have a single cluster-scoped issuer rather
than each namespace duplicating the issuers and secrets.

This changeset introduces a "cluster resource namespace", which defaults
to the same namespace as the controller (eg, "origin-ca-issuer"). A
ClusterOriginIssuer resource type has been added which resolves
SecretKeySelectors in this namespace. Other than determing what
namespace to resolve the secret, the implementation is the same as the
existing OriginIssuer.

Fixes: #24
  • Loading branch information
terinjokes committed Jun 5, 2024
1 parent fd52ada commit 09592e1
Show file tree
Hide file tree
Showing 17 changed files with 722 additions and 62 deletions.
25 changes: 22 additions & 3 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,32 @@ func main() {
os.Exit(1)
}

err = builder.
ControllerManagedBy(mgr).
For(&v1.ClusterOriginIssuer{}).
Complete(reconcile.AsReconciler(mgr.GetClient(), &controllers.ClusterOriginIssuerController{
Client: mgr.GetClient(),
Reader: mgr.GetAPIReader(),
ClusterResourceNamespace: o.ClusterResourceNamespace,
Clock: clock.RealClock{},
Factory: f,
Log: log.WithName("controllers").WithName("ClusterOriginIssuer"),
}))

if err != nil {
log.Error(err, "could not create cluster origin issuer controller")
os.Exit(1)
}

err = builder.
ControllerManagedBy(mgr).
For(&certmanager.CertificateRequest{}).
Complete(reconcile.AsReconciler(mgr.GetClient(), &controllers.CertificateRequestController{
Client: mgr.GetClient(),
Reader: mgr.GetAPIReader(),
Log: log.WithName("controllers").WithName("CertificateRequest"),
Client: mgr.GetClient(),
Reader: mgr.GetAPIReader(),
ClusterResourceNamespace: o.ClusterResourceNamespace,
Factory: f,
Log: log.WithName("controllers").WithName("CertificateRequest"),

Clock: clock.RealClock{},
CheckApprovedCondition: !o.DisableApprovedCheck,
Expand Down
10 changes: 8 additions & 2 deletions cmd/controller/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
)

type ControllerOptions struct {
KubernetesAPIQPS float32
KubernetesAPIBurst int
KubernetesAPIQPS float32
KubernetesAPIBurst int
ClusterResourceNamespace string

DisableApprovedCheck bool
}
Expand All @@ -29,6 +30,7 @@ func (o *ControllerOptions) AddFlags(fs *pflag.FlagSet) {
fs.Float32Var(&o.KubernetesAPIQPS, "kube-api-qps", defaultKubernetesAPIQPS, "Maximium queries-per-second of requests to the Kubernetes apiserver.")
fs.IntVar(&o.KubernetesAPIBurst, "kube-api-burst", defaultKubernetesAPIBurst, "Maximium queries-per-second burst of request send to the Kubernetes apiserver.")
fs.BoolVar(&o.DisableApprovedCheck, "disable-approved-check", o.DisableApprovedCheck, "Disables waiting for CertificateRequests to have an approved condition before signing.")
fs.StringVar(&o.ClusterResourceNamespace, "cluster-resource-namespace", o.ClusterResourceNamespace, "Namespace used for cluster-scoped resources, such as secrets used by ClusterOriginIssuer")
}

func (o *ControllerOptions) Validate() error {
Expand All @@ -40,5 +42,9 @@ func (o *ControllerOptions) Validate() error {
return fmt.Errorf("invalid value for kube-api-qps: %v must be higher than 0", o.KubernetesAPIQPS)
}

if o.ClusterResourceNamespace == "" {
return fmt.Errorf("invalid value for cluster-resource-namespace: must be set")
}

return nil
}
119 changes: 119 additions & 0 deletions deploy/crds/cert-manager.k8s.cloudflare.com_clusteroriginissuers.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.13.0
name: clusteroriginissuers.cert-manager.k8s.cloudflare.com
spec:
group: cert-manager.k8s.cloudflare.com
names:
kind: ClusterOriginIssuer
listKind: ClusterOriginIssuerList
plural: clusteroriginissuers
singular: clusteroriginissuer
scope: Cluster
versions:
- name: v1
schema:
openAPIV3Schema:
description: A ClusterOriginIssuer represents the Cloudflare Origin CA as
an external cert-manager issuer. It is scoped to a single namespace, so
it can be used only by resources in the same namespace.
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: Spec is the desired state of the ClusterOriginIssuer resource.
properties:
auth:
description: Auth configures how to authenticate with the Cloudflare
API.
properties:
serviceKeyRef:
description: ServiceKeyRef authenticates with an API Service Key.
properties:
key:
description: Key of the secret to select from. Must be a valid
secret key.
type: string
name:
description: Name of the secret in the issuer's namespace
to select. If a cluster-scoped issuer, the secret is selected
from the "cluster resource namespace" configured on the
controller.
type: string
required:
- key
- name
type: object
type: object
requestType:
description: RequestType is the signature algorithm Cloudflare should
use to sign the certificate.
enum:
- OriginRSA
- OriginECC
type: string
required:
- auth
- requestType
type: object
status:
description: Status of the ClusterOriginIssuer. This is set and managed
automatically.
properties:
conditions:
description: List of status conditions to indicate the status of an
OriginIssuer Known condition types are `Ready`.
items:
description: OriginIssuerCondition contains condition information
for the OriginIssuer.
properties:
lastTransitionTime:
description: LastTransitionTime is the timestamp corresponding
to the last status change of this condition.
format: date-time
type: string
message:
description: Message is a human readable description of the
details of the last transition1, complementing reason.
type: string
reason:
description: Reason is a brief machine readable explanation
for the condition's last transition.
type: string
status:
description: Status of the condition, one of ('True', 'False',
'Unknown')
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: Type of the condition, known values are ('Ready')
enum:
- Ready
type: string
required:
- status
- type
type: object
type: array
type: object
type: object
served: true
storage: true
subresources:
status: {}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ spec:
secret key.
type: string
name:
description: Name of the secret in the OriginIssuer's namespace
to select from.
description: Name of the secret in the issuer's namespace
to select. If a cluster-scoped issuer, the secret is selected
from the "cluster resource namespace" configured on the
controller.
type: string
required:
- key
Expand Down
6 changes: 4 additions & 2 deletions deploy/manifests/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ spec:
spec:
serviceAccountName: originissuer-control
containers:
- image: cloudflare/origin-ca-issuer:v0.8.0
name: origin-ca-controller
- name: origin-ca-controller
image: cloudflare/origin-ca-issuer:v0.8.0
args:
- --cluster-resource-namespace=origin-ca-issuer
resources:
limits:
cpu: "1"
Expand Down
18 changes: 9 additions & 9 deletions deploy/rbac/role-approver.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@

---
# permissions to approve all cert-manager.k8s.cloudflare.com requests
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cert-manager-controller-approve:cert-manager-k8s-cloudflare-com
rules:
- apiGroups:
- cert-manager.io
resources:
- signers
verbs:
- approve
resourceNames:
- originissuers.cert-manager.k8s.cloudflare.com/*
- apiGroups:
- cert-manager.io
resources:
- signers
verbs:
- approve
resourceNames:
- originissuers.cert-manager.k8s.cloudflare.com/*
- clusteroriginissuers.cert-manager.k8s.cloudflare.com/*
17 changes: 17 additions & 0 deletions deploy/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ rules:
- get
- patch
- update
- apiGroups:
- cert-manager.k8s.cloudflare.com
resources:
- clusteroriginissuers
verbs:
- create
- get
- list
- watch
- apiGroups:
- cert-manager.k8s.cloudflare.com
resources:
- clusteroriginissuers/status
verbs:
- get
- patch
- update
- apiGroups:
- cert-manager.k8s.cloudflare.com
resources:
Expand Down
2 changes: 1 addition & 1 deletion pkgs/apis/v1/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ var (
)

func init() {
SchemeBuilder.Register(&OriginIssuer{}, &OriginIssuerList{})
SchemeBuilder.Register(&OriginIssuer{}, &OriginIssuerList{}, &ClusterOriginIssuer{}, &ClusterOriginIssuerList{})
}
33 changes: 32 additions & 1 deletion pkgs/apis/v1/types_originissuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,35 @@ type OriginIssuerList struct {
Items []OriginIssuer `json:"items"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:subresource:status

// A ClusterOriginIssuer represents the Cloudflare Origin CA as an external cert-manager issuer.
// It is scoped to a single namespace, so it can be used only by resources in the same
// namespace.
type ClusterOriginIssuer struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec is the desired state of the ClusterOriginIssuer resource.
Spec OriginIssuerSpec `json:"spec,omitempty"`

// Status of the ClusterOriginIssuer. This is set and managed automatically.
// +optional
Status OriginIssuerStatus `json:"status,omitempty"`
}

// +kubebuilder:object:root=true

// ClusterOriginIssuerList is a list of OriginIssuers.
type ClusterOriginIssuerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata.omitempty"`

Items []ClusterOriginIssuer `json:"items"`
}

// OriginIssuerSpec is the specification of an OriginIssuer. This includes any
// configuration required for the issuer.
type OriginIssuerSpec struct {
Expand Down Expand Up @@ -60,7 +89,9 @@ type OriginIssuerAuthentication struct {

// SecretKeySelector contains a reference to a secret.
type SecretKeySelector struct {
// Name of the secret in the OriginIssuer's namespace to select from.
// Name of the secret in the issuer's namespace to select. If a cluster-scoped
// issuer, the secret is selected from the "cluster resource namespace" configured
// on the controller.
Name string `json:"name"`
// Key of the secret to select from. Must be a valid secret key.
Key string `json:"key"`
Expand Down
59 changes: 59 additions & 0 deletions pkgs/apis/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 09592e1

Please sign in to comment.