serviceAccountRef: the vault issuer can now use bound SA tokens
Previously, the Vault issuer was only able to use a Secret in order to use the "Kubernetes authentication" method. The downside to this service account Secret token is that it has the default JWT iss "kubernetes/serviceaccount" (along with the fact that the token is not bound to a particular pod and has no expiry). With the new serviceAccountRef, cert-manager now requests the token on behalf of the pod in order to authenticate with Vault. Signed-off-by: Maël Valais <mael@vls.dev>
This commit is contained in:
parent
ba0bb5d503
commit
76eef68730
@ -70,7 +70,6 @@ rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["events"]
|
||||
verbs: ["create", "patch"]
|
||||
|
||||
---
|
||||
|
||||
# ClusterIssuer controller role
|
||||
|
||||
@ -1127,7 +1127,6 @@ spec:
|
||||
type: object
|
||||
required:
|
||||
- role
|
||||
- secretRef
|
||||
properties:
|
||||
mountPath:
|
||||
description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used.
|
||||
@ -1147,6 +1146,22 @@ spec:
|
||||
name:
|
||||
description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
description: A reference to a service account that will be used to request a bound token (also known as "projected token"). Compared to using "secretRef", using this field means that you don't rely on statically bound tokens. To use this field, you must configure an RBAC rule to let cert-manager request a token. See <link to a page in cert-manager.io> to learn more.
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
audience:
|
||||
description: Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver.
|
||||
type: string
|
||||
expirationSeconds:
|
||||
description: ExpirationSeconds is the requested duration of validity of the service account token. Defaults to 1 hour and must be at least 10 minutes.
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
description: Name of the ServiceAccount used to request a token.
|
||||
type: string
|
||||
tokenSecretRef:
|
||||
description: TokenSecretRef authenticates with Vault by presenting a token.
|
||||
type: object
|
||||
|
||||
@ -1127,7 +1127,6 @@ spec:
|
||||
type: object
|
||||
required:
|
||||
- role
|
||||
- secretRef
|
||||
properties:
|
||||
mountPath:
|
||||
description: The Vault mountPath here is the mount path to use when authenticating with Vault. For example, setting a value to `/v1/auth/foo`, will use the path `/v1/auth/foo/login` to authenticate with Vault. If unspecified, the default value "/v1/auth/kubernetes" will be used.
|
||||
@ -1147,6 +1146,22 @@ spec:
|
||||
name:
|
||||
description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
description: A reference to a service account that will be used to request a bound token (also known as "projected token"). Compared to using "secretRef", using this field means that you don't rely on statically bound tokens. To use this field, you must configure an RBAC rule to let cert-manager request a token. See <link to a page in cert-manager.io> to learn more.
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
audience:
|
||||
description: Audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver.
|
||||
type: string
|
||||
expirationSeconds:
|
||||
description: ExpirationSeconds is the requested duration of validity of the service account token. Defaults to 1 hour and must be at least 10 minutes.
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
description: Name of the ServiceAccount used to request a token.
|
||||
type: string
|
||||
tokenSecretRef:
|
||||
description: TokenSecretRef authenticates with Vault by presenting a token.
|
||||
type: object
|
||||
|
||||
@ -243,14 +243,41 @@ type VaultKubernetesAuth struct {
|
||||
|
||||
// The required Secret field containing a Kubernetes ServiceAccount JWT used
|
||||
// for authenticating with Vault. Use of 'ambient credentials' is not
|
||||
// supported.
|
||||
// supported. This field should not be set if serviceAccountRef is set.
|
||||
SecretRef cmmeta.SecretKeySelector
|
||||
|
||||
// A reference to a service account that will be used to request a bound
|
||||
// token (also known as "projected token"). Compared to using "secretRef",
|
||||
// using this field means that you don't rely on statically bound tokens. To
|
||||
// use this field, you must configure an RBAC rule to let cert-manager
|
||||
// request a token. See <link to a page in cert-manager.io> to learn more.
|
||||
// +optional
|
||||
ServiceAccountRef ServiceAccountRef `json:"serviceAccountRef,omitempty"`
|
||||
|
||||
// A required field containing the Vault Role to assume. A Role binds a
|
||||
// Kubernetes ServiceAccount with a set of Vault policies.
|
||||
Role string
|
||||
}
|
||||
|
||||
// ServiceAccountRef is a service account used by cert-manager to request a
|
||||
// token.
|
||||
type ServiceAccountRef struct {
|
||||
// Name of the ServiceAccount used to request a token.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Audience is the intended audience of the token. A recipient of a token
|
||||
// must identify itself with an identifier specified in the audience of the
|
||||
// token, and otherwise should reject the token. The audience defaults to the
|
||||
// identifier of the apiserver.
|
||||
// +optional
|
||||
Audience string `json:"audience,omitempty"`
|
||||
|
||||
// ExpirationSeconds is the requested duration of validity of the service
|
||||
// account token. Defaults to 1 hour and must be at least 10 minutes.
|
||||
// +optional
|
||||
ExpirationSeconds int64 `json:"expirationSeconds,omitempty"`
|
||||
}
|
||||
|
||||
// CAIssuer configures an issuer that can issue certificates from its provided
|
||||
// CA certificate. It contains the name of the private key to sign certificates,
|
||||
// holds the location for Certificate Revocation Lists (CRL) distribution
|
||||
|
||||
@ -294,6 +294,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.ServiceAccountRef)(nil), (*certmanager.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(a.(*v1.ServiceAccountRef), b.(*certmanager.ServiceAccountRef), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*certmanager.ServiceAccountRef)(nil), (*v1.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(a.(*certmanager.ServiceAccountRef), b.(*v1.ServiceAccountRef), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.VaultAppRole)(nil), (*certmanager.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_VaultAppRole_To_certmanager_VaultAppRole(a.(*v1.VaultAppRole), b.(*certmanager.VaultAppRole), scope)
|
||||
}); err != nil {
|
||||
@ -1277,6 +1287,30 @@ func Convert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(in *certmanager
|
||||
return autoConvert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(in *v1.ServiceAccountRef, out *certmanager.ServiceAccountRef, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Audience = in.Audience
|
||||
out.ExpirationSeconds = in.ExpirationSeconds
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef is an autogenerated conversion function.
|
||||
func Convert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(in *v1.ServiceAccountRef, out *certmanager.ServiceAccountRef, s conversion.Scope) error {
|
||||
return autoConvert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(in *certmanager.ServiceAccountRef, out *v1.ServiceAccountRef, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Audience = in.Audience
|
||||
out.ExpirationSeconds = in.ExpirationSeconds
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef is an autogenerated conversion function.
|
||||
func Convert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(in *certmanager.ServiceAccountRef, out *v1.ServiceAccountRef, s conversion.Scope) error {
|
||||
return autoConvert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_VaultAppRole_To_certmanager_VaultAppRole(in *v1.VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error {
|
||||
out.Path = in.Path
|
||||
out.RoleId = in.RoleId
|
||||
@ -1432,6 +1466,9 @@ func autoConvert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *v
|
||||
if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(&in.ServiceAccountRef, &out.ServiceAccountRef, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Role = in.Role
|
||||
return nil
|
||||
}
|
||||
@ -1446,6 +1483,9 @@ func autoConvert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(in *c
|
||||
if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Convert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(&in.ServiceAccountRef, &out.ServiceAccountRef, s); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Role = in.Role
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -125,3 +125,7 @@ func Convert_certmanager_CertificateRequestSpec_To_v1alpha2_CertificateRequestSp
|
||||
out.CSRPEM = in.Request
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error {
|
||||
return autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(in, out, s)
|
||||
}
|
||||
|
||||
@ -265,7 +265,8 @@ type VaultKubernetesAuth struct {
|
||||
// The required Secret field containing a Kubernetes ServiceAccount JWT used
|
||||
// for authenticating with Vault. Use of 'ambient credentials' is not
|
||||
// supported.
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef"`
|
||||
// +optional
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef,omitempty"`
|
||||
|
||||
// A required field containing the Vault Role to assume. A Role binds a
|
||||
// Kubernetes ServiceAccount with a set of Vault policies.
|
||||
|
||||
@ -1462,15 +1462,11 @@ func autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth
|
||||
if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: in.ServiceAccountRef requires manual conversion: does not exist in peer-type
|
||||
out.Role = in.Role
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth is an autogenerated conversion function.
|
||||
func Convert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error {
|
||||
return autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha2_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error {
|
||||
out.URL = in.URL
|
||||
if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil {
|
||||
|
||||
@ -111,3 +111,8 @@ func Convert_certmanager_CertificateRequestSpec_To_v1alpha3_CertificateRequestSp
|
||||
out.CSRPEM = in.Request
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth is an autogenerated conversion function.
|
||||
func Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error {
|
||||
return autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(in, out, s)
|
||||
}
|
||||
|
||||
@ -265,7 +265,8 @@ type VaultKubernetesAuth struct {
|
||||
// The required Secret field containing a Kubernetes ServiceAccount JWT used
|
||||
// for authenticating with Vault. Use of 'ambient credentials' is not
|
||||
// supported.
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef"`
|
||||
// +optional
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef,omitempty"`
|
||||
|
||||
// A required field containing the Vault Role to assume. A Role binds a
|
||||
// Kubernetes ServiceAccount with a set of Vault policies.
|
||||
|
||||
@ -312,11 +312,6 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*certmanager.VaultKubernetesAuth)(nil), (*VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(a.(*certmanager.VaultKubernetesAuth), b.(*VaultKubernetesAuth), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*VenafiCloud)(nil), (*certmanager.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud(a.(*VenafiCloud), b.(*certmanager.VenafiCloud), scope)
|
||||
}); err != nil {
|
||||
@ -367,6 +362,11 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*certmanager.VaultKubernetesAuth)(nil), (*VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(a.(*certmanager.VaultKubernetesAuth), b.(*VaultKubernetesAuth), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddConversionFunc((*certmanager.X509Subject)(nil), (*X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_certmanager_X509Subject_To_v1alpha3_X509Subject(a.(*certmanager.X509Subject), b.(*X509Subject), scope)
|
||||
}); err != nil {
|
||||
@ -1461,15 +1461,11 @@ func autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth
|
||||
if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: in.ServiceAccountRef requires manual conversion: does not exist in peer-type
|
||||
out.Role = in.Role
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth is an autogenerated conversion function.
|
||||
func Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error {
|
||||
return autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error {
|
||||
out.URL = in.URL
|
||||
if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil {
|
||||
|
||||
10
internal/apis/certmanager/v1beta1/conversion.go
Normal file
10
internal/apis/certmanager/v1beta1/conversion.go
Normal file
@ -0,0 +1,10 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
certmanager "github.com/cert-manager/cert-manager/internal/apis/certmanager"
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
)
|
||||
|
||||
func Convert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error {
|
||||
return autoConvert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(in, out, s)
|
||||
}
|
||||
@ -267,7 +267,8 @@ type VaultKubernetesAuth struct {
|
||||
// The required Secret field containing a Kubernetes ServiceAccount JWT used
|
||||
// for authenticating with Vault. Use of 'ambient credentials' is not
|
||||
// supported.
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef"`
|
||||
// +optional
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef,omitempty"`
|
||||
|
||||
// A required field containing the Vault Role to assume. A Role binds a
|
||||
// Kubernetes ServiceAccount with a set of Vault policies.
|
||||
|
||||
@ -1454,15 +1454,11 @@ func autoConvert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(
|
||||
if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: in.ServiceAccountRef requires manual conversion: does not exist in peer-type
|
||||
out.Role = in.Role
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth is an autogenerated conversion function.
|
||||
func Convert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error {
|
||||
return autoConvert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error {
|
||||
out.URL = in.URL
|
||||
if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil {
|
||||
|
||||
@ -817,6 +817,22 @@ func (in *SelfSignedIssuer) DeepCopy() *SelfSignedIssuer {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef.
|
||||
func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceAccountRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) {
|
||||
*out = *in
|
||||
@ -896,6 +912,7 @@ func (in *VaultIssuer) DeepCopy() *VaultIssuer {
|
||||
func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) {
|
||||
*out = *in
|
||||
out.SecretRef = in.SecretRef
|
||||
out.ServiceAccountRef = in.ServiceAccountRef
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -18,18 +18,20 @@ package fake
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
type FakeClient struct {
|
||||
NewRequestS *vault.Request
|
||||
RawRequestFn func(r *vault.Request) (*vault.Response, error)
|
||||
token string
|
||||
GotToken string
|
||||
T *testing.T
|
||||
}
|
||||
|
||||
func NewFakeClient() *Client {
|
||||
return &Client{
|
||||
func NewFakeClient() *FakeClient {
|
||||
return &FakeClient{
|
||||
NewRequestS: new(vault.Request),
|
||||
RawRequestFn: func(r *vault.Request) (*vault.Response, error) {
|
||||
return nil, errors.New("unexpected RawRequest call")
|
||||
@ -37,30 +39,33 @@ func NewFakeClient() *Client {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) WithNewRequest(r *vault.Request) *Client {
|
||||
func (c *FakeClient) WithNewRequest(r *vault.Request) *FakeClient {
|
||||
c.NewRequestS = r
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) WithRawRequest(resp *vault.Response, err error) *Client {
|
||||
func (c *FakeClient) WithRawRequest(resp *vault.Response, err error) *FakeClient {
|
||||
c.RawRequestFn = func(r *vault.Request) (*vault.Response, error) {
|
||||
return resp, err
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) NewRequest(method, requestPath string) *vault.Request {
|
||||
func (c *FakeClient) WithRawRequestFn(fn func(t *testing.T, r *vault.Request) (*vault.Response, error)) *FakeClient {
|
||||
c.RawRequestFn = func(req *vault.Request) (*vault.Response, error) {
|
||||
return fn(c.T, req)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *FakeClient) NewRequest(method, requestPath string) *vault.Request {
|
||||
return c.NewRequestS
|
||||
}
|
||||
|
||||
func (c *Client) SetToken(v string) {
|
||||
c.token = v
|
||||
func (c *FakeClient) SetToken(v string) {
|
||||
c.GotToken = v
|
||||
}
|
||||
|
||||
func (c *Client) Token() string {
|
||||
return c.token
|
||||
}
|
||||
|
||||
func (c *Client) RawRequest(r *vault.Request) (*vault.Response, error) {
|
||||
func (c *FakeClient) RawRequest(r *vault.Request) (*vault.Response, error) {
|
||||
return c.RawRequestFn(r)
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package vault
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -28,6 +29,8 @@ import (
|
||||
|
||||
vault "github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/sdk/helper/certutil"
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
|
||||
v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
|
||||
@ -39,8 +42,7 @@ var _ Interface = &Vault{}
|
||||
|
||||
// ClientBuilder is a function type that returns a new Interface.
|
||||
// Can be used in tests to create a mock signer of Vault certificate requests.
|
||||
type ClientBuilder func(namespace string, secretsLister corelisters.SecretLister,
|
||||
issuer v1.GenericIssuer) (Interface, error)
|
||||
type ClientBuilder func(namespace string, _ func(ns string) CreateToken, _ corelisters.SecretLister, _ v1.GenericIssuer) (Interface, error)
|
||||
|
||||
// Interface implements various high level functionality related to connecting
|
||||
// with a Vault server, verifying its status and signing certificate request for
|
||||
@ -57,9 +59,13 @@ type Client interface {
|
||||
SetToken(v string)
|
||||
}
|
||||
|
||||
// For mocking purposes.
|
||||
type CreateToken func(ctx context.Context, saName string, req *authv1.TokenRequest, opts metav1.CreateOptions) (*authv1.TokenRequest, error)
|
||||
|
||||
// Vault implements Interface and holds a Vault issuer, secrets lister and a
|
||||
// Vault client.
|
||||
type Vault struct {
|
||||
createToken CreateToken // Uses the same namespace as below.
|
||||
secretsLister corelisters.SecretLister
|
||||
issuer v1.GenericIssuer
|
||||
namespace string
|
||||
@ -86,8 +92,9 @@ type Vault struct {
|
||||
// secrets lister.
|
||||
// Returned errors may be network failures and should be considered for
|
||||
// retrying.
|
||||
func New(namespace string, secretsLister corelisters.SecretLister, issuer v1.GenericIssuer) (Interface, error) {
|
||||
func New(namespace string, createTokenFn func(ns string) CreateToken, secretsLister corelisters.SecretLister, issuer v1.GenericIssuer) (Interface, error) {
|
||||
v := &Vault{
|
||||
createToken: createTokenFn(namespace),
|
||||
secretsLister: secretsLister,
|
||||
namespace: namespace,
|
||||
issuer: issuer,
|
||||
@ -197,7 +204,7 @@ func (v *Vault) setToken(client Client) error {
|
||||
if kubernetesAuth != nil {
|
||||
token, err := v.requestTokenWithKubernetesAuth(client, kubernetesAuth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading Kubernetes service account token from %s: %s", kubernetesAuth.SecretRef.Name, err.Error())
|
||||
return fmt.Errorf("while requesting a Vault token using the Kubernetes auth: %w", err)
|
||||
}
|
||||
client.SetToken(token)
|
||||
return nil
|
||||
@ -361,22 +368,41 @@ func (v *Vault) requestTokenWithAppRoleRef(client Client, appRole *v1.VaultAppRo
|
||||
}
|
||||
|
||||
func (v *Vault) requestTokenWithKubernetesAuth(client Client, kubernetesAuth *v1.VaultKubernetesAuth) (string, error) {
|
||||
secret, err := v.secretsLister.Secrets(v.namespace).Get(kubernetesAuth.SecretRef.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var jwt string
|
||||
switch {
|
||||
case kubernetesAuth.SecretRef.Name != "":
|
||||
secret, err := v.secretsLister.Secrets(v.namespace).Get(kubernetesAuth.SecretRef.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key := kubernetesAuth.SecretRef.Key
|
||||
if key == "" {
|
||||
key = v1.DefaultVaultTokenAuthSecretKey
|
||||
}
|
||||
key := kubernetesAuth.SecretRef.Key
|
||||
if key == "" {
|
||||
key = v1.DefaultVaultTokenAuthSecretKey
|
||||
}
|
||||
|
||||
keyBytes, ok := secret.Data[key]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("no data for %q in secret '%s/%s'", key, v.namespace, kubernetesAuth.SecretRef.Name)
|
||||
}
|
||||
keyBytes, ok := secret.Data[key]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("no data for %q in secret '%s/%s'", key, v.namespace, kubernetesAuth.SecretRef.Name)
|
||||
}
|
||||
|
||||
jwt := string(keyBytes)
|
||||
jwt = string(keyBytes)
|
||||
|
||||
case kubernetesAuth.ServiceAccountRef.Name != "":
|
||||
tokenrequest, err := v.createToken(context.Background(), kubernetesAuth.ServiceAccountRef.Name, &authv1.TokenRequest{
|
||||
Spec: authv1.TokenRequestSpec{
|
||||
Audiences: []string{kubernetesAuth.ServiceAccountRef.Audience},
|
||||
ExpirationSeconds: &kubernetesAuth.ServiceAccountRef.ExpirationSeconds,
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("while requesting a token for the service account %s/%s: %s", v.issuer.GetNamespace(), kubernetesAuth.ServiceAccountRef.Name, err.Error())
|
||||
}
|
||||
|
||||
jwt = tokenrequest.Status.Token
|
||||
default:
|
||||
return "", fmt.Errorf("programmer mistake: both serviceAccountRef.name and tokenRef.name are empty")
|
||||
}
|
||||
|
||||
parameters := map[string]string{
|
||||
"role": kubernetesAuth.Role,
|
||||
@ -390,7 +416,7 @@ func (v *Vault) requestTokenWithKubernetesAuth(client Client, kubernetesAuth *v1
|
||||
|
||||
url := filepath.Join(mountPath, "login")
|
||||
request := client.NewRequest("POST", url)
|
||||
err = request.SetJSONBody(parameters)
|
||||
err := request.SetJSONBody(parameters)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error encoding Vault parameters: %s", err.Error())
|
||||
}
|
||||
@ -464,3 +490,16 @@ func (v *Vault) IsVaultInitializedAndUnsealed() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Vault) addVaultNamespaceToRequest(request *vault.Request) {
|
||||
vaultIssuer := v.issuer.GetSpec().Vault
|
||||
if vaultIssuer != nil && vaultIssuer.Namespace != "" {
|
||||
if request.Headers != nil {
|
||||
request.Headers.Add("X-VAULT-NAMESPACE", vaultIssuer.Namespace)
|
||||
} else {
|
||||
vaultReqHeaders := http.Header{}
|
||||
vaultReqHeaders.Add("X-VAULT-NAMESPACE", vaultIssuer.Namespace)
|
||||
request.Headers = vaultReqHeaders
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package vault
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
@ -35,6 +36,7 @@ import (
|
||||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
authv1 "k8s.io/api/authentication/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
clientcorev1 "k8s.io/client-go/listers/core/v1"
|
||||
@ -171,7 +173,7 @@ func generateCSR(t *testing.T, secretKey crypto.Signer) []byte {
|
||||
type testSignT struct {
|
||||
issuer *cmapi.Issuer
|
||||
fakeLister *listers.FakeSecretLister
|
||||
fakeClient *vaultfake.Client
|
||||
fakeClient *vaultfake.FakeClient
|
||||
|
||||
csrPEM []byte
|
||||
expectedErr error
|
||||
@ -364,15 +366,6 @@ func TestExtractCertificatesFromVaultCertificateSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testSetTokenT struct {
|
||||
expectedToken string
|
||||
expectedErr error
|
||||
|
||||
issuer *cmapi.Issuer
|
||||
fakeLister *listers.FakeSecretLister
|
||||
fakeClient *vaultfake.Client
|
||||
}
|
||||
|
||||
func TestSetToken(t *testing.T) {
|
||||
tokenSecret := &corev1.Secret{
|
||||
Data: map[string][]byte{
|
||||
@ -391,7 +384,16 @@ func TestSetToken(t *testing.T) {
|
||||
"my-kube-key": []byte("my-secret-kube-token"),
|
||||
},
|
||||
}
|
||||
tests := map[string]testSetTokenT{
|
||||
tests := map[string]struct {
|
||||
expectedToken string
|
||||
expectedErr error
|
||||
|
||||
issuer *cmapi.Issuer
|
||||
fakeLister *listers.FakeSecretLister
|
||||
mockCreateToken func(t *testing.T) CreateToken
|
||||
|
||||
fakeClient *vaultfake.FakeClient
|
||||
}{
|
||||
"if neither token secret ref, app role secret ref, or kube auth then not found then error": {
|
||||
issuer: gen.Issuer("vault-issuer",
|
||||
gen.SetIssuerVault(cmapi.VaultIssuer{
|
||||
@ -400,7 +402,6 @@ func TestSetToken(t *testing.T) {
|
||||
}),
|
||||
),
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister()),
|
||||
fakeClient: vaultfake.NewFakeClient(),
|
||||
expectedToken: "",
|
||||
expectedErr: errors.New(
|
||||
"error initializing Vault client: tokenSecretRef, appRoleSecretRef, or Kubernetes auth role not set",
|
||||
@ -423,7 +424,6 @@ func TestSetToken(t *testing.T) {
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret does not exists")),
|
||||
),
|
||||
fakeClient: vaultfake.NewFakeClient(),
|
||||
expectedToken: "",
|
||||
expectedErr: errors.New("secret does not exists"),
|
||||
},
|
||||
@ -445,7 +445,7 @@ func TestSetToken(t *testing.T) {
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(tokenSecret, nil),
|
||||
),
|
||||
fakeClient: vaultfake.NewFakeClient(),
|
||||
|
||||
expectedToken: "my-secret-token",
|
||||
expectedErr: nil,
|
||||
},
|
||||
@ -470,7 +470,6 @@ func TestSetToken(t *testing.T) {
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret not found")),
|
||||
),
|
||||
fakeClient: vaultfake.NewFakeClient(),
|
||||
expectedToken: "",
|
||||
expectedErr: errors.New("secret not found"),
|
||||
},
|
||||
@ -527,7 +526,6 @@ func TestSetToken(t *testing.T) {
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret does not exists")),
|
||||
),
|
||||
fakeClient: vaultfake.NewFakeClient(),
|
||||
expectedToken: "",
|
||||
expectedErr: errors.New("error reading Kubernetes service account token from secret-ref-name: secret does not exists"),
|
||||
},
|
||||
@ -552,7 +550,6 @@ func TestSetToken(t *testing.T) {
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(&corev1.Secret{}, nil),
|
||||
),
|
||||
fakeClient: vaultfake.NewFakeClient(),
|
||||
expectedToken: "",
|
||||
expectedErr: errors.New(`error reading Kubernetes service account token from secret-ref-name: no data for "my-kube-key" in secret 'test-namespace/secret-ref-name'`),
|
||||
},
|
||||
@ -614,7 +611,7 @@ func TestSetToken(t *testing.T) {
|
||||
expectedErr: nil,
|
||||
},
|
||||
|
||||
"if app role secret ref and token secret set, take preference on token secret": {
|
||||
"if appRole.secretRef, tokenSecretRef set, take preference on tokenSecretRef": {
|
||||
issuer: gen.Issuer("vault-issuer",
|
||||
gen.SetIssuerVault(cmapi.VaultIssuer{
|
||||
CABundle: []byte(testLeafCertificate),
|
||||
@ -640,17 +637,65 @@ func TestSetToken(t *testing.T) {
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(tokenSecret, nil),
|
||||
),
|
||||
fakeClient: vaultfake.NewFakeClient(),
|
||||
expectedToken: "my-secret-token",
|
||||
expectedErr: nil,
|
||||
},
|
||||
|
||||
"if kubernetes.serviceAccountRef set, request token and exchange it for a vault token": {
|
||||
issuer: gen.Issuer("vault-issuer",
|
||||
gen.SetIssuerVault(cmapi.VaultIssuer{
|
||||
CABundle: []byte(testLeafCertificate),
|
||||
Auth: cmapi.VaultAuth{
|
||||
Kubernetes: &cmapi.VaultKubernetesAuth{
|
||||
Role: "kube-vault-role",
|
||||
ServiceAccountRef: v1.ServiceAccountRef{
|
||||
Name: "my-service-account",
|
||||
Audience: "my-audience",
|
||||
ExpirationSeconds: 100,
|
||||
},
|
||||
Path: "my-path",
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
mockCreateToken: func(t *testing.T) CreateToken {
|
||||
return func(_ context.Context, saName string, req *authv1.TokenRequest, _ metav1.CreateOptions) (*authv1.TokenRequest, error) {
|
||||
assert.Equal(t, "my-service-account", saName)
|
||||
assert.Equal(t, "my-audience", req.Spec.Audiences[0])
|
||||
assert.Equal(t, int64(100), *req.Spec.ExpirationSeconds)
|
||||
return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{
|
||||
Token: "kube-sa-token",
|
||||
}}, nil
|
||||
}
|
||||
},
|
||||
fakeClient: vaultfake.NewFakeClient().WithRawRequestFn(func(t *testing.T, req *vault.Request) (*vault.Response, error) {
|
||||
// Vault exhanges the Kubernetes token with a Vault token.
|
||||
assert.Equal(t, "kube-sa-token", req.Obj.(map[string]string)["jwt"])
|
||||
assert.Equal(t, "kube-vault-role", req.Obj.(map[string]string)["role"])
|
||||
return &vault.Response{Response: &http.Response{Body: io.NopCloser(strings.NewReader(
|
||||
`{"request_id":"","lease_id":"","lease_duration":0,"renewable":false,"data":null,"warnings":null,"data":{"id":"vault-token"}}`,
|
||||
))}}, nil
|
||||
}),
|
||||
expectedToken: "vault-token",
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
if test.fakeClient == nil {
|
||||
test.fakeClient = &vaultfake.FakeClient{T: t}
|
||||
} else {
|
||||
test.fakeClient.T = t
|
||||
}
|
||||
var mockCreateToken CreateToken
|
||||
if test.mockCreateToken != nil {
|
||||
mockCreateToken = test.mockCreateToken(t)
|
||||
}
|
||||
v := &Vault{
|
||||
namespace: "test-namespace",
|
||||
secretsLister: test.fakeLister,
|
||||
createToken: mockCreateToken,
|
||||
issuer: test.issuer,
|
||||
}
|
||||
|
||||
@ -662,9 +707,9 @@ func TestSetToken(t *testing.T) {
|
||||
test.expectedErr, err)
|
||||
}
|
||||
|
||||
if test.fakeClient.Token() != test.expectedToken {
|
||||
if test.fakeClient.GotToken != test.expectedToken {
|
||||
t.Errorf("got unexpected client token, exp=%s got=%s",
|
||||
test.expectedToken, test.fakeClient.Token())
|
||||
test.expectedToken, test.fakeClient.GotToken)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -873,7 +918,8 @@ type testNewConfigT struct {
|
||||
issuer *cmapi.Issuer
|
||||
checkFunc func(cfg *vault.Config, err error) error
|
||||
|
||||
fakeLister *listers.FakeSecretLister
|
||||
fakeLister *listers.FakeSecretLister
|
||||
fakeCreateToken func(t *testing.T) CreateToken
|
||||
}
|
||||
|
||||
func TestNewConfig(t *testing.T) {
|
||||
@ -1023,6 +1069,30 @@ func TestNewConfig(t *testing.T) {
|
||||
expectedErr: errors.New("no Vault CA bundles loaded, check bundle contents"),
|
||||
fakeLister: caBundleSecretRefFakeSecretLister("test-namespace", "bundle", "my-bundle.crt", "not a valid certificate"),
|
||||
},
|
||||
"the tokenCreate func should be called with the correct namespace": {
|
||||
issuer: gen.Issuer("vault-issuer",
|
||||
gen.SetIssuerVault(cmapi.VaultIssuer{
|
||||
Path: "my-path",
|
||||
Auth: cmapi.VaultAuth{
|
||||
Kubernetes: &cmapi.VaultKubernetesAuth{
|
||||
Role: "my-role",
|
||||
ServiceAccountRef: v1.ServiceAccountRef{
|
||||
Name: "my-sa",
|
||||
Audience: "my-audience",
|
||||
ExpirationSeconds: 100,
|
||||
},
|
||||
},
|
||||
}})),
|
||||
fakeCreateToken: func(t *testing.T) CreateToken {
|
||||
return func(_ context.Context, saName string, req *authv1.TokenRequest, opts metav1.CreateOptions) (*authv1.TokenRequest, error) {
|
||||
assert.Equal(t, "test-namespace", req.Namespace)
|
||||
assert.Equal(t, "my-sa", saName)
|
||||
return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{
|
||||
Token: "foo",
|
||||
}}, nil
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
@ -1079,7 +1149,6 @@ func TestRequestTokenWithAppRoleRef(t *testing.T) {
|
||||
|
||||
tests := map[string]requestTokenWithAppRoleRefT{
|
||||
"a secret reference that does not exist should error": {
|
||||
client: vaultfake.NewFakeClient(),
|
||||
appRole: basicAppRoleRef,
|
||||
fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret not found")),
|
||||
@ -1200,6 +1269,7 @@ func TestNewWithVaultNamespaces(t *testing.T) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c, err := New(
|
||||
"k8s-ns1",
|
||||
func(ns string) CreateToken { return nil },
|
||||
listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(
|
||||
&corev1.Secret{
|
||||
@ -1254,6 +1324,7 @@ func TestIsVaultInitiatedAndUnsealedIntegration(t *testing.T) {
|
||||
|
||||
v, err := New(
|
||||
"k8s-ns1",
|
||||
func(ns string) CreateToken { return nil },
|
||||
listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(
|
||||
&corev1.Secret{
|
||||
@ -1318,6 +1389,7 @@ func TestSignIntegration(t *testing.T) {
|
||||
|
||||
v, err := New(
|
||||
"k8s-ns1",
|
||||
func(ns string) CreateToken { return nil },
|
||||
listers.FakeSecretListerFrom(listers.NewFakeSecretLister(),
|
||||
listers.SetFakeSecretNamespaceListerGet(
|
||||
&corev1.Secret{
|
||||
|
||||
@ -269,13 +269,41 @@ type VaultKubernetesAuth struct {
|
||||
// The required Secret field containing a Kubernetes ServiceAccount JWT used
|
||||
// for authenticating with Vault. Use of 'ambient credentials' is not
|
||||
// supported.
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef"`
|
||||
// +optional
|
||||
SecretRef cmmeta.SecretKeySelector `json:"secretRef,omitempty"`
|
||||
|
||||
// A reference to a service account that will be used to request a bound
|
||||
// token (also known as "projected token"). Compared to using "secretRef",
|
||||
// using this field means that you don't rely on statically bound tokens. To
|
||||
// use this field, you must configure an RBAC rule to let cert-manager
|
||||
// request a token. See <link to a page in cert-manager.io> to learn more.
|
||||
// +optional
|
||||
ServiceAccountRef ServiceAccountRef `json:"serviceAccountRef,omitempty"`
|
||||
|
||||
// A required field containing the Vault Role to assume. A Role binds a
|
||||
// Kubernetes ServiceAccount with a set of Vault policies.
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// ServiceAccountRef is a service account used by cert-manager to request a
|
||||
// token.
|
||||
type ServiceAccountRef struct {
|
||||
// Name of the ServiceAccount used to request a token.
|
||||
Name string `json:"name"`
|
||||
|
||||
// Audience is the intended audience of the token. A recipient of a token
|
||||
// must identify itself with an identifier specified in the audience of the
|
||||
// token, and otherwise should reject the token. The audience defaults to the
|
||||
// identifier of the apiserver.
|
||||
// +optional
|
||||
Audience string `json:"audience,omitempty"`
|
||||
|
||||
// ExpirationSeconds is the requested duration of validity of the service
|
||||
// account token. Defaults to 1 hour and must be at least 10 minutes.
|
||||
// +optional
|
||||
ExpirationSeconds int64 `json:"expirationSeconds,omitempty"`
|
||||
}
|
||||
|
||||
type CAIssuer struct {
|
||||
// SecretName is the name of the secret used to sign Certificates issued
|
||||
// by this Issuer.
|
||||
|
||||
@ -817,6 +817,22 @@ func (in *SelfSignedIssuer) DeepCopy() *SelfSignedIssuer {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef.
|
||||
func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceAccountRef)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) {
|
||||
*out = *in
|
||||
@ -896,6 +912,7 @@ func (in *VaultIssuer) DeepCopy() *VaultIssuer {
|
||||
func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) {
|
||||
*out = *in
|
||||
out.SecretRef = in.SecretRef
|
||||
out.ServiceAccountRef = in.ServiceAccountRef
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ const (
|
||||
// pkg/controller/certificaterequests.Issuer interface.
|
||||
type Vault struct {
|
||||
issuerOptions controllerpkg.IssuerOptions
|
||||
createTokenFn func(ns string) vaultinternal.CreateToken
|
||||
secretsLister corelisters.SecretLister
|
||||
reporter *crutil.Reporter
|
||||
|
||||
@ -59,7 +60,10 @@ func init() {
|
||||
// NewVault returns a new Vault instance with the given controller context.
|
||||
func NewVault(ctx *controllerpkg.Context) certificaterequests.Issuer {
|
||||
return &Vault{
|
||||
issuerOptions: ctx.IssuerOptions,
|
||||
issuerOptions: ctx.IssuerOptions,
|
||||
createTokenFn: func(ns string) vaultinternal.CreateToken {
|
||||
return ctx.Client.CoreV1().ServiceAccounts(ns).CreateToken
|
||||
},
|
||||
secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
|
||||
reporter: crutil.NewReporter(ctx.Clock, ctx.Recorder),
|
||||
vaultClientBuilder: vaultinternal.New,
|
||||
@ -74,7 +78,7 @@ func (v *Vault) Sign(ctx context.Context, cr *v1.CertificateRequest, issuerObj v
|
||||
|
||||
resourceNamespace := v.issuerOptions.ResourceNamespace(issuerObj)
|
||||
|
||||
client, err := v.vaultClientBuilder(resourceNamespace, v.secretsLister, issuerObj)
|
||||
client, err := v.vaultClientBuilder(resourceNamespace, v.createTokenFn, v.secretsLister, issuerObj)
|
||||
if k8sErrors.IsNotFound(err) {
|
||||
message := "Required secret resource not found"
|
||||
|
||||
|
||||
@ -518,7 +518,7 @@ func runTest(t *testing.T, test testT) {
|
||||
vault := NewVault(test.builder.Context).(*Vault)
|
||||
|
||||
if test.fakeVault != nil {
|
||||
vault.vaultClientBuilder = func(ns string, sl corelisters.SecretLister,
|
||||
vault.vaultClientBuilder = func(ns string, _ func(ns string) internalvault.CreateToken, sl corelisters.SecretLister,
|
||||
iss cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
return test.fakeVault.New(ns, sl, iss)
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import (
|
||||
certificatesv1 "k8s.io/api/certificates/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
@ -49,6 +50,7 @@ type signingFn func(*x509.Certificate, *x509.Certificate, crypto.PublicKey, inte
|
||||
// using Vault Issuers.
|
||||
type Vault struct {
|
||||
issuerOptions controllerpkg.IssuerOptions
|
||||
kclient kubernetes.Interface
|
||||
secretsLister corelisters.SecretLister
|
||||
|
||||
recorder record.EventRecorder
|
||||
@ -71,6 +73,7 @@ func init() {
|
||||
func NewVault(ctx *controllerpkg.Context) certificatesigningrequests.Signer {
|
||||
return &Vault{
|
||||
issuerOptions: ctx.IssuerOptions,
|
||||
kclient: ctx.Client,
|
||||
secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
|
||||
recorder: ctx.Recorder,
|
||||
certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(),
|
||||
@ -89,7 +92,8 @@ func (v *Vault) Sign(ctx context.Context, csr *certificatesv1.CertificateSigning
|
||||
|
||||
resourceNamespace := v.issuerOptions.ResourceNamespace(issuerObj)
|
||||
|
||||
client, err := v.clientBuilder(resourceNamespace, v.secretsLister, issuerObj)
|
||||
createTokenFn := func(ns string) internalvault.CreateToken { return v.kclient.CoreV1().ServiceAccounts(ns).CreateToken }
|
||||
client, err := v.clientBuilder(resourceNamespace, createTokenFn, v.secretsLister, issuerObj)
|
||||
if apierrors.IsNotFound(err) {
|
||||
message := "Required secret resource not found"
|
||||
log.Error(err, message)
|
||||
|
||||
@ -129,7 +129,7 @@ func TestProcessItem(t *testing.T) {
|
||||
Status: corev1.ConditionTrue,
|
||||
}),
|
||||
),
|
||||
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
clientBuilder: func(_ string, _ func(ns string) internalvault.CreateToken, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
return nil, apierrors.NewNotFound(schema.GroupResource{}, "test-secret")
|
||||
},
|
||||
builder: &testpkg.Builder{
|
||||
@ -190,7 +190,7 @@ func TestProcessItem(t *testing.T) {
|
||||
Status: corev1.ConditionTrue,
|
||||
}),
|
||||
),
|
||||
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
clientBuilder: func(_ string, _ func(ns string) internalvault.CreateToken, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
return nil, errors.New("generic error")
|
||||
},
|
||||
expectedErr: true,
|
||||
@ -234,7 +234,7 @@ func TestProcessItem(t *testing.T) {
|
||||
Status: corev1.ConditionTrue,
|
||||
}),
|
||||
),
|
||||
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
clientBuilder: func(_ string, _ func(ns string) internalvault.CreateToken, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
return fakevault.New(), nil
|
||||
},
|
||||
builder: &testpkg.Builder{
|
||||
@ -296,7 +296,7 @@ func TestProcessItem(t *testing.T) {
|
||||
Status: corev1.ConditionTrue,
|
||||
}),
|
||||
),
|
||||
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
clientBuilder: func(_ string, _ func(ns string) internalvault.CreateToken, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
return fakevault.New().WithSign(nil, nil, errors.New("sign error")), nil
|
||||
},
|
||||
builder: &testpkg.Builder{
|
||||
@ -357,7 +357,7 @@ func TestProcessItem(t *testing.T) {
|
||||
Status: corev1.ConditionTrue,
|
||||
}),
|
||||
),
|
||||
clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
clientBuilder: func(_ string, _ func(ns string) internalvault.CreateToken, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) {
|
||||
return fakevault.New().WithSign([]byte("signed-cert"), []byte("signing-ca"), nil), nil
|
||||
},
|
||||
builder: &testpkg.Builder{
|
||||
|
||||
@ -40,7 +40,9 @@ const (
|
||||
messageAuthFieldsRequired = "Vault tokenSecretRef, appRole, or kubernetes is required"
|
||||
messageMultipleAuthFieldsSet = "Multiple auth methods cannot be set on the same Vault issuer"
|
||||
|
||||
messageKubeAuthFieldsRequired = "Vault Kubernetes auth requires both role and secretRef.name"
|
||||
messageKubeAuthRoleRequired = "Vault Kubernetes auth requires a role to be set"
|
||||
messageKubeAuthEitherRequired = "Vault Kubernetes auth requires either secretRef.name or serviceAccountRef.name to be set"
|
||||
messageKubeAuthSingleRequired = "Vault Kubernetes auth cannot be used with both secretRef.name and serviceAccountRef.name"
|
||||
messageTokenAuthNameRequired = "Vault Token auth requires tokenSecretRef.name"
|
||||
messageAppRoleAuthFieldsRequired = "Vault AppRole auth requires both roleId and tokenSecretRef.name"
|
||||
)
|
||||
@ -95,14 +97,31 @@ func (v *Vault) Setup(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if all mandatory Vault Kubernetes fields are set.
|
||||
if kubeAuth != nil && (len(kubeAuth.SecretRef.Name) == 0 || len(kubeAuth.Role) == 0) {
|
||||
logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageKubeAuthFieldsRequired)
|
||||
apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthFieldsRequired)
|
||||
// When using the Kubernetes auth, giving a role is mandatory.
|
||||
if kubeAuth != nil && len(kubeAuth.Role) == 0 {
|
||||
logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageKubeAuthRoleRequired)
|
||||
apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthRoleRequired)
|
||||
return nil
|
||||
}
|
||||
|
||||
client, err := vaultinternal.New(v.resourceNamespace, v.secretsLister, v.issuer)
|
||||
// When using the Kubernetes auth, you must either set secretRef or
|
||||
// serviceAccountRef.
|
||||
if kubeAuth != nil && (len(kubeAuth.SecretRef.Name) == 0 && len(kubeAuth.ServiceAccountRef.Name) == 0) {
|
||||
logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageKubeAuthEitherRequired)
|
||||
apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthEitherRequired)
|
||||
return nil
|
||||
}
|
||||
|
||||
// When using the Kubernetes auth, you can't use secretRef and
|
||||
// serviceAccountRef simultaneously.
|
||||
if kubeAuth != nil && (len(kubeAuth.SecretRef.Name) != 0 && len(kubeAuth.ServiceAccountRef.Name) != 0) {
|
||||
logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageKubeAuthSingleRequired)
|
||||
apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthSingleRequired)
|
||||
return nil
|
||||
}
|
||||
|
||||
createTokenFn := func(ns string) vaultinternal.CreateToken { return v.Client.CoreV1().ServiceAccounts(ns).CreateToken }
|
||||
client, err := vaultinternal.New(v.resourceNamespace, createTokenFn, v.secretsLister, v.issuer)
|
||||
if err != nil {
|
||||
s := messageVaultClientInitFailed + err.Error()
|
||||
logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, s)
|
||||
|
||||
@ -434,6 +434,15 @@ func (v *VaultInitializer) setupKubernetesBasedAuth() error {
|
||||
params := map[string]string{
|
||||
"kubernetes_host": v.APIServerURL,
|
||||
"kubernetes_ca_cert": v.APIServerCA,
|
||||
// Since Vault 1.9, HashiCorp recommends disabling the iss validation.
|
||||
// If we don't disable the iss validation, we can't use the same
|
||||
// Kubernetes auth config for both testing the "secretRef" Kubernetes
|
||||
// auth and the "serviceAccountRef" Kubernetes auth because the former
|
||||
// relies on static tokens for which "iss" is
|
||||
// "kubernetes/serviceaccount", and the later relies on bound tokens for
|
||||
// which "iss" is "https://kubernetes.default.svc.cluster.local".
|
||||
// https://www.vaultproject.io/docs/auth/kubernetes#kubernetes-1-21
|
||||
"disable_iss_validation": "true",
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("/v1/auth/%s/config", v.KubernetesAuthPath)
|
||||
@ -551,3 +560,69 @@ func (v *VaultInitializer) CleanKubernetesRole(client kubernetes.Interface, vaul
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RoleAndBindingForServiceAccountRefAuth(roleName, namespace, serviceAccount string) (*rbacv1.Role, *rbacv1.RoleBinding) {
|
||||
return &rbacv1.Role{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: roleName,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Rules: []rbacv1.PolicyRule{
|
||||
{
|
||||
APIGroups: []string{""},
|
||||
Resources: []string{"serviceaccounts/token"},
|
||||
ResourceNames: []string{serviceAccount},
|
||||
Verbs: []string{"create"},
|
||||
},
|
||||
},
|
||||
},
|
||||
&rbacv1.RoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: roleName,
|
||||
},
|
||||
RoleRef: rbacv1.RoleRef{
|
||||
APIGroup: "rbac.authorization.k8s.io",
|
||||
Kind: "Role",
|
||||
Name: roleName,
|
||||
},
|
||||
Subjects: []rbacv1.Subject{
|
||||
{
|
||||
Name: "cert-manager",
|
||||
Namespace: "cert-manager",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateKubernetesRoleForServiceAccountRefAuth creates a service account and a
|
||||
// role for using the "serviceAccountRef" field.
|
||||
func CreateKubernetesRoleForServiceAccountRefAuth(client kubernetes.Interface, roleName, saNS, saName string) error {
|
||||
role, binding := RoleAndBindingForServiceAccountRefAuth(roleName, saNS, saName)
|
||||
_, err := client.RbacV1().Roles(saNS).Create(context.TODO(), role, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating Role for Kubernetes auth ServiceAccount with serviceAccountRef: %s", err.Error())
|
||||
}
|
||||
_, err = client.RbacV1().RoleBindings(saNS).Create(context.TODO(), binding, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating RoleBinding for Kubernetes auth ServiceAccount with serviceAccountRef: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CleanKubernetesRoleForServiceAccountRefAuth(client kubernetes.Interface, roleName, saNS, saName string) error {
|
||||
if err := client.RbacV1().RoleBindings(saNS).Delete(context.TODO(), roleName, metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := client.RbacV1().Roles(saNS).Delete(context.TODO(), roleName, metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := client.CoreV1().ServiceAccounts(saNS).Delete(context.TODO(), saName, metav1.DeleteOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -188,7 +188,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
|
||||
gen.SetIssuerVaultURL(vault.Details().Host),
|
||||
gen.SetIssuerVaultPath(vaultPath),
|
||||
gen.SetIssuerVaultCABundle(vault.Details().VaultCA),
|
||||
gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
gen.SetIssuerVaultKubernetesAuthSecret("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
_, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -209,7 +209,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
|
||||
gen.SetIssuerVaultURL(vault.Details().Host),
|
||||
gen.SetIssuerVaultPath(vaultPath),
|
||||
gen.SetIssuerVaultCABundle(vault.Details().VaultCA),
|
||||
gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
gen.SetIssuerVaultKubernetesAuthSecret("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
_, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
By("Waiting for Issuer to become Ready")
|
||||
@ -258,7 +258,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
|
||||
gen.SetIssuerVaultURL(vault.Details().Host),
|
||||
gen.SetIssuerVaultPath(vaultPath),
|
||||
gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"),
|
||||
gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
gen.SetIssuerVaultKubernetesAuthSecret("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
_, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -281,7 +281,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
|
||||
gen.SetIssuerVaultURL(vault.Details().Host),
|
||||
gen.SetIssuerVaultPath(vaultPath),
|
||||
gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"),
|
||||
gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
gen.SetIssuerVaultKubernetesAuthSecret("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
_, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -335,7 +335,7 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
|
||||
gen.SetIssuerVaultURL(vault.Details().Host),
|
||||
gen.SetIssuerVaultPath(vaultPath),
|
||||
gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"),
|
||||
gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
gen.SetIssuerVaultKubernetesAuthSecret("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
_, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@ -369,4 +369,30 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
It("should be ready with a valid serviceAccountRef", func() {
|
||||
// Note that we reuse the same service account as for the Kubernetes
|
||||
// auth based on secretRef. There should be no problem doing so.
|
||||
By("Creating the Role and RoleBinding to let cert-manager use TokenRequest for the ServiceAccount")
|
||||
vaultaddon.CreateKubernetesRoleForServiceAccountRefAuth(f.KubeClientSet, vaultKubernetesRoleName, f.Namespace.Name, vaultSecretServiceAccount)
|
||||
defer vaultaddon.CleanKubernetesRoleForServiceAccountRefAuth(f.KubeClientSet, vaultKubernetesRoleName, f.Namespace.Name, vaultSecretServiceAccount)
|
||||
|
||||
By("Creating an Issuer")
|
||||
vaultIssuer := gen.Issuer(issuerName,
|
||||
gen.SetIssuerNamespace(f.Namespace.Name),
|
||||
gen.SetIssuerVaultURL(vault.Details().Host),
|
||||
gen.SetIssuerVaultPath(vaultPath),
|
||||
gen.SetIssuerVaultCABundle(vault.Details().VaultCA),
|
||||
gen.SetIssuerVaultKubernetesAuthServiceAccount(vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath))
|
||||
_, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Waiting for Issuer to become Ready")
|
||||
err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name),
|
||||
issuerName,
|
||||
v1.IssuerCondition{
|
||||
Type: v1.IssuerConditionReady,
|
||||
Status: cmmeta.ConditionTrue,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
@ -325,7 +325,7 @@ func SetIssuerVaultAppRoleAuth(keyName, approleName, roleId, path string) Issuer
|
||||
}
|
||||
}
|
||||
|
||||
func SetIssuerVaultKubernetesAuth(keyName, secretServiceAccount, role, path string) IssuerModifier {
|
||||
func SetIssuerVaultKubernetesAuthSecret(keyName, secretServiceAccount, role, path string) IssuerModifier {
|
||||
return func(iss v1.GenericIssuer) {
|
||||
spec := iss.GetSpec()
|
||||
if spec.Vault == nil {
|
||||
@ -344,6 +344,26 @@ func SetIssuerVaultKubernetesAuth(keyName, secretServiceAccount, role, path stri
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func SetIssuerVaultKubernetesAuthServiceAccount(serviceAccount, role, path string) IssuerModifier {
|
||||
return func(iss v1.GenericIssuer) {
|
||||
spec := iss.GetSpec()
|
||||
if spec.Vault == nil {
|
||||
spec.Vault = &v1.VaultIssuer{}
|
||||
}
|
||||
spec.Vault.Auth.Kubernetes = &v1.VaultKubernetesAuth{
|
||||
Path: path,
|
||||
Role: role,
|
||||
ServiceAccountRef: v1.ServiceAccountRef{
|
||||
Name: serviceAccount,
|
||||
Audience: "vault",
|
||||
ExpirationSeconds: 600,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func SetIssuerSelfSigned(a v1.SelfSignedIssuer) IssuerModifier {
|
||||
return func(iss v1.GenericIssuer) {
|
||||
iss.GetSpec().SelfSigned = &a
|
||||
|
||||
Loading…
Reference in New Issue
Block a user