diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml new file mode 100644 index 0000000..14f93bc --- /dev/null +++ b/.github/workflows/docker_build.yml @@ -0,0 +1,28 @@ +name: Docker build +on: + workflow_dispatch: + inputs: + tag: + description: Docker image tag + type: string + required: true +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + registry: registry.evertrust.io + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + - name: Build and push + uses: docker/build-push-action@v3 + with: + push: true + platforms: linux/amd64,linux/arm64 + tags: registry.evertrust.io/horizon-issuer:${{ inputs.tag }} \ No newline at end of file diff --git a/Readme.md b/Readme.md index c0f36de..0b9099f 100644 --- a/Readme.md +++ b/Readme.md @@ -4,7 +4,7 @@ ## Prerequisites This software has been testing against the following environment : -- Horizon version 2.1.0 and above +- Horizon version 2.2.0 and above - Kubernetes version 1.22 and above ## Installation @@ -76,10 +76,57 @@ metadata: > **Warning** : be sure to set the `cert-manager.io/common-name` annotation as by default, ingress-shim will generate certificates without any DN. This will cause errors on Horizon's side. +### Using labels, owners and teams +Horizon offers useful features to categorize and better understand your certificates through metadata. You may specify metadata at three levels : + +#### On an ingress object +You may use the following annotations on ingresses that will be reflected onto the enrolled certificate : +```yaml +horizon.evertrust.io/owner: owner-name +horizon.evertrust.io/team: team-name +``` + +#### On a certificate object +You may use the following annotations on the cert-manager `Certificate` object, that will be reflected onto the enrolled certificate : +```yaml +horizon.evertrust.io/owner: owner-name +horizon.evertrust.io/team: team-name +``` +These values, if set, will take precedence over annotations on an `Ingress` object. + +#### On a `ClusterIssuer` or `Issuer` object +You may configure your issuer to apply certain metadata to every certificate enrolled through it, by modifying its spec. The following keys are available : +```yaml +apiVersion: horizon.evertrust.io/v1alpha1 +kind: ClusterIssuer +spec: + owner: owner-name + team: team-name + labels: + label-key: label-value +``` +These values, if set, will take precedence over annotations on an `Ingress` or `Certificate` object. + ## Configuration -### Revoking deleted certificates +### Trusting custom CAs -By default, Horizon issuer does not revoke certificates deleted from Kubernetes as cert-manager can reuse the private key kept in the according secret. +Your Horizon instance may be presenting a certificate issued by your custom CA. To trust that certificate, you may specify a CA bundle when creating the issuer through the `caBundle` field. You may also completely disable TLS verification by setting `skipTLSVerify` to `true`, this is however highly discouraged. + +Example : +```yaml +apiVersion: horizon.evertrust.io/v1alpha1 +kind: ClusterIssuer +spec: + caBundle: | + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + skipTLSVerify: false +``` +You can also mount your custom `/etc/ssl/certs` directory if you wish to have more control over the underlying OS trust store. + +### Revoking deleted certificates -If you want to enable that behavior, set the `revokeCertificates` to `true` in your `values.yaml` file. \ No newline at end of file +By default, Horizon issuer does not revoke certificates deleted from Kubernetes as cert-manager can reuse the private key kept in the deleted certificate's secret. +If you want to revoke certificates are they are deleted, set the `revokeCertificates` property to `true` on your `Issuer` or `ClusterIssuer` object. When doing so, you may want to [clean up secrets as soon as certificates are revoked](https://cert-manager.io/docs/usage/certificate/#cleaning-up-secrets-when-certificates-are-deleted). \ No newline at end of file diff --git a/api/v1alpha1/issuer_types.go b/api/v1alpha1/issuer_types.go index 31c0fc2..2dbea6b 100644 --- a/api/v1alpha1/issuer_types.go +++ b/api/v1alpha1/issuer_types.go @@ -22,8 +22,8 @@ import ( // IssuerSpec defines the desired state of Issuer type IssuerSpec struct { - // URL is the base URL for the endpoint of the signing service, - // for example: "https://sample-signer.example.com/api". + // URL is the base URL of your Horizon instance, + // for instance: "https://horizon.yourcompany.com". URL string `json:"url"` // The Horizon Profile that will be used to enroll certificates. Your @@ -37,9 +37,34 @@ type IssuerSpec struct { // namespace that the controller runs in). AuthSecretName string `json:"authSecretName"` - // An optional string containing the CA bundle required to + // CaBundle contains the CA bundle required to // trust the Horizon endpoint certificate + // +optional CaBundle *string `json:"caBundle,omitempty"` + + // SkipTLSVerify indicates if untrusted certificates should be allowed + // when connecting to the Horizon instance. + // +optional + // +kubebuilder:default:=false + SkipTLSVerify bool `json:"skipTLSVerify"` + + // RevokeCertificates controls whether this issuer should revoke certificates + // that have been issued through it when their Kubernetes object is deleted. + // +kubebuilder:default:=false + // +optional + RevokeCertificates bool `json:"revokeCertificates"` + + // Labels is a map of labels that will override labels + // set at the Certificate or Ingress levels. + Labels map[string]string `json:"labels,omitempty"` + + // Owner will override the owner value set + // at the Certificate or Ingress levels. + Owner *string `json:"owner,omitempty"` + + // Team will override the team value set + // at the Certificate or Ingress levels. + Team *string `json:"team,omitempty"` } // IssuerStatus defines the observed state of Issuer diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 1bbee38..c0a101b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -170,6 +170,23 @@ func (in *IssuerSpec) DeepCopyInto(out *IssuerSpec) { *out = new(string) **out = **in } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Owner != nil { + in, out := &in.Owner, &out.Owner + *out = new(string) + **out = **in + } + if in.Team != nil { + in, out := &in.Team, &out.Team + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerSpec. diff --git a/charts/horizon-issuer/Chart.yaml b/charts/horizon-issuer/Chart.yaml index af3dcd8..150e276 100644 --- a/charts/horizon-issuer/Chart.yaml +++ b/charts/horizon-issuer/Chart.yaml @@ -1,24 +1,8 @@ apiVersion: v2 name: horizon-issuer -description: A Helm chart for Kubernetes - -# A chart can be either an 'application' or a 'library' chart. -# -# Application charts are a collection of templates that can be packaged into versioned archives -# to be deployed. -# -# Library charts provide useful utilities or functions for the chart developer. They're included as -# a dependency of application charts to inject those utilities and functions into the rendering -# pipeline. Library charts do not define any templates and therefore cannot be deployed. +description: Issue certificates seamlessly using Horizon and cert-manager. type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -# Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Versions are not expected to -# follow Semantic Versioning. They should reflect the version the application is using. -# It is recommended to use it with quotes. -appVersion: "1.16.0" +version: 0.0.1 +appVersion: "0.0.1" +sources: + - https://github.com/evertrust/horizon-issuer diff --git a/charts/horizon-issuer/crds/horizon.evertrust.io_clusterissuers.yaml b/charts/horizon-issuer/crds/horizon.evertrust.io_clusterissuers.yaml index bb64e5b..f3a7e81 100644 --- a/charts/horizon-issuer/crds/horizon.evertrust.io_clusterissuers.yaml +++ b/charts/horizon-issuer/crds/horizon.evertrust.io_clusterissuers.yaml @@ -57,16 +57,41 @@ spec: (and defaults to the namespace that the controller runs in). type: string caBundle: - description: An optional string containing the CA bundle required - to trust the Horizon endpoint certificate + description: CaBundle contains the CA bundle required to trust the + Horizon endpoint certificate + type: string + labels: + additionalProperties: + type: string + description: Labels is a map of labels that will override labels set + at the Certificate or Ingress levels. + type: object + owner: + description: Owner will override the owner value set at the Certificate + or Ingress levels. type: string profile: description: The Horizon Profile that will be used to enroll certificates. Your authenticated principal should have rights over this Profile. type: string + revokeCertificates: + default: false + description: RevokeCertificates controls whether this issuer should + revoke certificates that have been issued through it when their + Kubernetes object is deleted. + type: boolean + skipTLSVerify: + default: false + description: SkipTLSVerify indicates if untrusted certificates should + be allowed when connecting to the Horizon instance. + type: boolean + team: + description: Team will override the team value set at the Certificate + or Ingress levels. + type: string url: - description: 'URL is the base URL for the endpoint of the signing - service, for example: "https://sample-signer.example.com/api".' + description: 'URL is the base URL of your Horizon instance, for instance: + "https://horizon.yourcompany.com".' type: string required: - authSecretName diff --git a/charts/horizon-issuer/crds/horizon.evertrust.io_issuers.yaml b/charts/horizon-issuer/crds/horizon.evertrust.io_issuers.yaml index a5e0ed9..4eaee93 100644 --- a/charts/horizon-issuer/crds/horizon.evertrust.io_issuers.yaml +++ b/charts/horizon-issuer/crds/horizon.evertrust.io_issuers.yaml @@ -17,13 +17,21 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .profile + - jsonPath: .spec.profile name: Profile type: string + - jsonPath: .spec.url + name: Horizon URL + type: string + - jsonPath: .spec.authSecretName + name: Secret + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string name: v1alpha1 schema: openAPIV3Schema: - description: Issuer is the Schema for the issuers API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -48,16 +56,41 @@ spec: (and defaults to the namespace that the controller runs in). type: string caBundle: - description: An optional string containing the CA bundle required - to trust the Horizon endpoint certificate + description: CaBundle contains the CA bundle required to trust the + Horizon endpoint certificate + type: string + labels: + additionalProperties: + type: string + description: Labels is a map of labels that will override labels set + at the Certificate or Ingress levels. + type: object + owner: + description: Owner will override the owner value set at the Certificate + or Ingress levels. type: string profile: description: The Horizon Profile that will be used to enroll certificates. Your authenticated principal should have rights over this Profile. type: string + revokeCertificates: + default: false + description: RevokeCertificates controls whether this issuer should + revoke certificates that have been issued through it when their + Kubernetes object is deleted. + type: boolean + skipTLSVerify: + default: false + description: SkipTLSVerify indicates if untrusted certificates should + be allowed when connecting to the Horizon instance. + type: boolean + team: + description: Team will override the team value set at the Certificate + or Ingress levels. + type: string url: - description: 'URL is the base URL for the endpoint of the signing - service, for example: "https://sample-signer.example.com/api".' + description: 'URL is the base URL of your Horizon instance, for instance: + "https://horizon.yourcompany.com".' type: string required: - authSecretName @@ -107,8 +140,7 @@ spec: type: object served: true storage: true - subresources: - status: {} + subresources: {} status: acceptedNames: kind: "" diff --git a/charts/horizon-issuer/templates/deployment.yaml b/charts/horizon-issuer/templates/deployment.yaml index dcda49e..dae217d 100644 --- a/charts/horizon-issuer/templates/deployment.yaml +++ b/charts/horizon-issuer/templates/deployment.yaml @@ -38,9 +38,6 @@ spec: - /manager args: - --leader-elect - {{- if .Values.revokeCertificates }} - - --revoke-certificates - {{- end }} ports: - containerPort: 8080 name: http @@ -56,6 +53,10 @@ spec: port: 8081 initialDelaySeconds: 5 periodSeconds: 10 + {{- if .Values.volumeMounts }} + volumeMounts: + {{- toYaml .Values.volumeMounts | nindent 12 }} + {{- end }} env: {{- range $key, $value := .Values.env }} - name: {{ $key }} @@ -63,6 +64,10 @@ spec: {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.volumes }} + volumes: + {{- toYaml .Values.volumes | nindent 8 }} + {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/horizon-issuer/values.yaml b/charts/horizon-issuer/values.yaml index fce2851..42d0a44 100644 --- a/charts/horizon-issuer/values.yaml +++ b/charts/horizon-issuer/values.yaml @@ -1,12 +1,10 @@ # Default values for horizon-issuer. -revokeCertificates: true - replicaCount: 1 image: repository: registry.evertrust.io/horizon-issuer - tag: 0.0.1 + tag: 0.0.3 pullPolicy: IfNotPresent imagePullSecrets: [] @@ -38,6 +36,10 @@ podSecurityContext: securityContext: allowPrivilegeEscalation: false +volumeMounts: [] + +volumes: [] + resources: {} autoscaling: diff --git a/go.mod b/go.mod index 7bdc629..5c69061 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/evertrust/horizon-issuer go 1.16 require ( - github.com/evertrust/horizon-go v0.0.0-20220405090940-041e01ad81b4 + github.com/evertrust/horizon-go v0.0.3 github.com/jetstack/cert-manager v1.6.1 k8s.io/api v0.22.2 k8s.io/apimachinery v0.22.2 diff --git a/go.sum b/go.sum index 5a3ef67..88dad64 100644 --- a/go.sum +++ b/go.sum @@ -378,8 +378,8 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evertrust/horizon-go v0.0.0-20220405090940-041e01ad81b4 h1:6zDAZahxrDBtx+n1Fn1y5vqXAjtKOgz2QtudMPq8S+M= -github.com/evertrust/horizon-go v0.0.0-20220405090940-041e01ad81b4/go.mod h1:KmiC6YkLfhHPSpTjQ8/gt7i8jVrdmsURgOrqQKRoAD4= +github.com/evertrust/horizon-go v0.0.3 h1:isW1n6pPu8esI2CPKWLdi8SMezUh3aeO4n+xh7ud8Zk= +github.com/evertrust/horizon-go v0.0.3/go.mod h1:KmiC6YkLfhHPSpTjQ8/gt7i8jVrdmsURgOrqQKRoAD4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= diff --git a/internal/controllers/certificaterequest_controller.go b/internal/controllers/certificaterequest_controller.go index 479d99f..8f571b3 100644 --- a/internal/controllers/certificaterequest_controller.go +++ b/internal/controllers/certificaterequest_controller.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "github.com/evertrust/horizon-go/http" + "github.com/evertrust/horizon-go/requests" horizonapi "github.com/evertrust/horizon-issuer/api/v1alpha1" horizonissuer "github.com/evertrust/horizon-issuer/internal/issuer/horizon" issuerutil "github.com/evertrust/horizon-issuer/internal/issuer/util" @@ -27,6 +29,7 @@ import ( cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -35,16 +38,13 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" ) var ( errIssuerRef = errors.New("error interpreting issuerRef") errGetIssuer = errors.New("error getting issuer") errIssuerNotReady = errors.New("issuer is not ready") - errSignerBuilder = errors.New("failed to build the signer") - errSignerSign = errors.New("failed to sign") - errInvalidBaseUrl = errors.New("invalid base url") - errUnknownHorizon = errors.New("horizon returned an error") ) const FinalizerName = horizonissuer.IssuerNamespace + "/finalizer" @@ -56,7 +56,6 @@ type CertificateRequestReconciler struct { ClusterResourceNamespace string Clock clock.Clock Issuer horizonissuer.HorizonIssuer - RevokeCertificates bool } func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { @@ -90,41 +89,24 @@ func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.R ) } - // Ignore but log an error if the issuerRef.Kind is unrecognised - issuerGVK := horizonapi.GroupVersion.WithKind(certificateRequest.Spec.IssuerRef.Kind) - issuerRO, err := r.Scheme.New(issuerGVK) + issuer, err := r.issuerFromRequest(ctx, &certificateRequest) if err != nil { - err = fmt.Errorf("%w: %v", errIssuerRef, err) - log.Error(err, "Unrecognised kind. Ignoring.") - setReadyCondition(cmmeta.ConditionFalse, cmapi.CertificateRequestReasonFailed, err.Error()) - return ctrl.Result{}, nil - } - issuer := issuerRO.(client.Object) - // Create a Namespaced name for Issuer and a non-Namespaced name for ClusterIssuer - issuerName := types.NamespacedName{ - Name: certificateRequest.Spec.IssuerRef.Name, + log.Error(err, "Cannot find Issuer") + return ctrl.Result{}, fmt.Errorf("%w", err) } + var secretNamespace string - switch t := issuer.(type) { + switch issuer.(type) { case *horizonapi.Issuer: - issuerName.Namespace = certificateRequest.Namespace secretNamespace = certificateRequest.Namespace - log = log.WithValues("issuer", issuerName) + log = log.WithValues("issuer", issuer.GetName()) case *horizonapi.ClusterIssuer: secretNamespace = r.ClusterResourceNamespace - log = log.WithValues("clusterissuer", issuerName) + log = log.WithValues("clusterissuer", issuer.GetName()) default: - err := fmt.Errorf("unexpected issuer type: %v", t) - log.Error(err, "The issuerRef referred to a registered Kind which is not yet handled. Ignoring.") - setReadyCondition(cmmeta.ConditionFalse, cmapi.CertificateRequestReasonFailed, err.Error()) return ctrl.Result{}, nil } - // Get the Issuer or ClusterIssuer - if err := r.Get(ctx, issuerName, issuer); err != nil { - return ctrl.Result{}, fmt.Errorf("%w: %v", errGetIssuer, err) - } - issuerSpec, issuerStatus, err := issuerutil.GetSpecAndStatus(issuer) if err != nil { log.Error(err, "Unable to get the IssuerStatus. Ignoring.") @@ -156,7 +138,7 @@ func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.R r.Issuer.Client = *clientFromIssuer - if r.RevokeCertificates { + if issuerSpec.RevokeCertificates { // examine DeletionTimestamp to determine if object is under deletion if certificateRequest.ObjectMeta.DeletionTimestamp.IsZero() { // The object is not being deleted, so if it does not have our finalizer, @@ -238,7 +220,11 @@ func (r *CertificateRequestReconciler) Reconcile(ctx context.Context, req ctrl.R if _, ok := certificateRequest.Annotations[horizonissuer.RequestIdAnnotation]; ok { return r.Issuer.UpdateRequest(ctx, &certificateRequest) } else { - return r.Issuer.SubmitRequest(ctx, r.Client, issuerSpec.Profile, &certificateRequest) + labels, owner, team, err := r.certificateMetadata(ctx, &certificateRequest) + if err != nil { + setReadyCondition(cmmeta.ConditionFalse, cmapi.CertificateRequestReasonPending, err.Error()) + } + return r.Issuer.SubmitRequest(ctx, r.Client, *issuerSpec, labels, owner, team, &certificateRequest) } } @@ -250,8 +236,12 @@ func (r *CertificateRequestReconciler) handleDeletion(ctx context.Context, certi // our finalizer is present, so lets handle any external dependency if err := r.Issuer.RevokeCertificate(ctx, certificateRequest); err != nil { // if fail to delete the external dependency here, return with error - // so that it can be retried - return err + // so that it can be retried, except if the error is from Horizon + if _, isHorizonError := err.(*http.HorizonErrorResponse); !isHorizonError { + return err + } else { + log.FromContext(ctx).Info(fmt.Sprintf("Horizon returned an error when revoking the certificate : %s. Marking the certificate as revoked to avoid a loop.", err.Error())) + } } // remove our finalizer from the list and update it. @@ -264,6 +254,136 @@ func (r *CertificateRequestReconciler) handleDeletion(ctx context.Context, certi return nil } +func (r *CertificateRequestReconciler) certificateMetadata(ctx context.Context, certificateRequest *cmapi.CertificateRequest) ([]requests.LabelElement, *string, *string, error) { + // Récupérer le certificat + var owner *string + var team *string + var labels []requests.LabelElement + + certificate, err := r.certificateFromRequest(ctx, certificateRequest) + if err != nil { + return nil, nil, nil, err + } + issuer, err := r.issuerFromRequest(ctx, certificateRequest) + if err != nil { + return nil, nil, nil, err + } + ingress, err := r.ingressFromCertificate(ctx, certificate) + if err != nil { + return nil, nil, nil, err + } + + if ingress != nil { + ownerString := ingress.Annotations[horizonissuer.OwnerAnnotation] + if ownerString != "" { + owner = &ownerString + } + teamString := ingress.Annotations[horizonissuer.TeamAnnotation] + if teamString != "" { + owner = &teamString + } + } + + if certificate != nil { + ownerString := certificate.Annotations[horizonissuer.OwnerAnnotation] + if ownerString != "" { + owner = &ownerString + } + teamString := certificate.Annotations[horizonissuer.TeamAnnotation] + if teamString != "" { + owner = &teamString + } + } + + issuerSpec, _, err := issuerutil.GetSpecAndStatus(issuer) + if err != nil { + return nil, nil, nil, err + } + + if issuerSpec.Owner != nil { + owner = issuerSpec.Owner + } + + if issuerSpec.Team != nil { + team = issuerSpec.Team + } + + if len(issuerSpec.Labels) > 0 { + for k, v := range issuerSpec.Labels { + labels = append(labels, requests.LabelElement{ + Label: k, + Value: v, + }) + } + } + + return labels, owner, team, nil +} + +// issuerFromRequest returns the Issuer of a given CertificateRequest. +func (r *CertificateRequestReconciler) issuerFromRequest(ctx context.Context, certificateRequest *cmapi.CertificateRequest) (client.Object, error) { + issuerGVK := horizonapi.GroupVersion.WithKind(certificateRequest.Spec.IssuerRef.Kind) + issuerRO, err := r.Scheme.New(issuerGVK) + if err != nil { + err = fmt.Errorf("%w: %v", errIssuerRef, err) + return nil, err + } + issuer := issuerRO.(client.Object) + // Create a Namespaced name for Issuer and a non-Namespaced name for ClusterIssuer + issuerName := types.NamespacedName{ + Name: certificateRequest.Spec.IssuerRef.Name, + } + switch t := issuer.(type) { + case *horizonapi.Issuer: + issuerName.Namespace = certificateRequest.Namespace + case *horizonapi.ClusterIssuer: + default: + err := fmt.Errorf("unexpected issuer type: %v", t) + return nil, err + } + + // Get the Issuer or ClusterIssuer + if err := r.Get(ctx, issuerName, issuer); err != nil { + return nil, fmt.Errorf("%w: %v", errGetIssuer, err) + } + + return issuer, nil + +} + +// certificateFromRequest returns the Certificate object associated with that CertificateRequest +func (r *CertificateRequestReconciler) certificateFromRequest(ctx context.Context, certificateRequest *cmapi.CertificateRequest) (*cmapi.Certificate, error) { + certificateName := types.NamespacedName{ + Namespace: certificateRequest.Namespace, + Name: certificateRequest.Annotations["cert-manager.io/certificate-name"], + } + + var certificate cmapi.Certificate + err := r.Get(ctx, certificateName, &certificate) + + return &certificate, err +} + +func (r *CertificateRequestReconciler) ingressFromCertificate(ctx context.Context, certificate *cmapi.Certificate) (*v1.Ingress, error) { + var ingressName *types.NamespacedName + for _, ref := range certificate.OwnerReferences { + if ref.APIVersion == "networking.k8s.io/v1" && ref.Kind == "Ingress" { + ingressName = &types.NamespacedName{ + Namespace: certificate.Namespace, + Name: ref.Name, + } + } + } + + if ingressName == nil { + return nil, nil + } + + var ingress v1.Ingress + err := r.Get(ctx, *ingressName, &ingress) + return &ingress, err +} + func (r *CertificateRequestReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&cmapi.CertificateRequest{}). diff --git a/internal/issuer/horizon/issuer.go b/internal/issuer/horizon/issuer.go index 892caef..498b344 100644 --- a/internal/issuer/horizon/issuer.go +++ b/internal/issuer/horizon/issuer.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/evertrust/horizon-go" "github.com/evertrust/horizon-go/requests" + "github.com/evertrust/horizon-issuer/api/v1alpha1" cmutil "github.com/jetstack/cert-manager/pkg/api/util" cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1" @@ -16,20 +17,26 @@ import ( ) const IssuerNamespace = "horizon.evertrust.io" -const RequestIdAnnotation = IssuerNamespace + "/request-id" +const ( + RequestIdAnnotation = IssuerNamespace + "/request-id" + OwnerAnnotation = IssuerNamespace + "/owner" + TeamAnnotation = IssuerNamespace + "/team" +) type HorizonIssuer struct { Client horizon.Horizon } -func (r *HorizonIssuer) SubmitRequest(ctx context.Context, client client.Client, profile string, certificateRequest *cmapi.CertificateRequest) (result ctrl.Result, err error) { +func (r *HorizonIssuer) SubmitRequest(ctx context.Context, client client.Client, issuer v1alpha1.IssuerSpec, labels []requests.LabelElement, owner *string, team *string, certificateRequest *cmapi.CertificateRequest) (result ctrl.Result, err error) { logger := log.FromContext(ctx) - logger.Info(fmt.Sprintf("Submitting request %s to profile %s", certificateRequest.UID, profile)) + logger.Info(fmt.Sprintf("Submitting request %s to profile %s", certificateRequest.UID, issuer.Profile)) request, err := r.Client.Requests.DecentralizedEnroll( - profile, + issuer.Profile, certificateRequest.Spec.Request, - []requests.LabelElement{}, + labels, + owner, + team, ) if err != nil { return ctrl.Result{}, fmt.Errorf("%w: %v", errors.New("unable to sign the CSR using Horizon"), err) diff --git a/internal/issuer/horizon/util.go b/internal/issuer/horizon/util.go index a668125..553b479 100644 --- a/internal/issuer/horizon/util.go +++ b/internal/issuer/horizon/util.go @@ -22,5 +22,9 @@ func HorizonClientFromIssuer(issuerSpec *horizonapi.IssuerSpec, secretData map[s client.Http.SetCaBundle(*issuerSpec.CaBundle) } + if issuerSpec.SkipTLSVerify { + client.Http.SkipTLSVerify() + } + return client, nil } diff --git a/main.go b/main.go index d13b1e2..4fc861a 100644 --- a/main.go +++ b/main.go @@ -59,13 +59,11 @@ func init() { } func main() { - var revokeCertificates bool var metricsAddr string var enableLeaderElection bool var clusterResourceNamespace string var probeAddr string var printVersion bool - flag.BoolVar(&revokeCertificates, "revoke-certificates", false, "Enable revocation of deleted certificates.") flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.StringVar(&clusterResourceNamespace, "cluster-resource-namespace", "", "The namespace for secrets in which cluster-scoped resources are found.") @@ -148,7 +146,6 @@ func main() { ClusterResourceNamespace: clusterResourceNamespace, Clock: clock.RealClock{}, Issuer: horizon.HorizonIssuer{}, - RevokeCertificates: revokeCertificates, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "CertificateRequest") os.Exit(1)