2020-09-16 14:19:51 +00:00
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
2020-07-08 17:06:44 +00:00
2020-08-20 17:54:15 +00:00
// Package server is the command line entry point for pinniped-server.
2020-07-27 20:32:14 +00:00
package server
2020-07-08 17:06:44 +00:00
import (
2020-07-13 19:30:16 +00:00
"context"
"fmt"
2020-07-08 17:06:44 +00:00
"io"
2020-08-19 18:21:07 +00:00
"time"
2020-07-08 17:06:44 +00:00
2020-07-27 12:55:33 +00:00
"github.com/spf13/cobra"
2020-09-21 18:16:14 +00:00
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
2020-08-25 01:07:34 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-07-23 15:05:21 +00:00
genericapiserver "k8s.io/apiserver/pkg/server"
genericoptions "k8s.io/apiserver/pkg/server/options"
2020-08-19 18:21:07 +00:00
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
2020-08-25 01:07:34 +00:00
"k8s.io/klog/v2"
2020-07-16 19:24:30 +00:00
2020-09-18 21:38:45 +00:00
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
2020-09-18 22:15:04 +00:00
loginv1alpha1 "go.pinniped.dev/generated/1.19/apis/login/v1alpha1"
2020-09-18 19:56:24 +00:00
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
"go.pinniped.dev/internal/apiserver"
"go.pinniped.dev/internal/certauthority/kubecertauthority"
"go.pinniped.dev/internal/controller/identityprovider/idpcache"
"go.pinniped.dev/internal/controller/issuerconfig"
"go.pinniped.dev/internal/controllermanager"
"go.pinniped.dev/internal/downward"
"go.pinniped.dev/internal/here"
"go.pinniped.dev/internal/provider"
"go.pinniped.dev/internal/registry/credentialrequest"
"go.pinniped.dev/pkg/config"
2020-09-21 18:16:14 +00:00
configapi "go.pinniped.dev/pkg/config/api"
)
// These constants are various label/annotation keys used in Pinniped. They are namespaced by
// a "pinniped.dev" child domain so they don't collide with other keys.
const (
// kubeCertAgentLabelKey is used to identify which pods are created by the kube-cert-agent
// controllers.
kubeCertAgentLabelKey = "kube-cert-agent.pinniped.dev"
// kubeCertAgentCertPathAnnotationKey is the annotation that the kube-cert-agent pod will use
// to communicate the in-pod path to the kube API's certificate.
kubeCertAgentCertPathAnnotationKey = "kube-cert-agent.pinniped.dev/cert-path"
// kubeCertAgentKeyPathAnnotationKey is the annotation that the kube-cert-agent pod will use
// to communicate the in-pod path to the kube API's key.
kubeCertAgentKeyPathAnnotationKey = "kube-cert-agent.pinniped.dev/key-path"
2020-07-08 17:06:44 +00:00
)
2020-08-20 17:54:15 +00:00
// App is an object that represents the pinniped-server application.
2020-07-08 17:06:44 +00:00
type App struct {
2020-08-11 01:53:53 +00:00
cmd * cobra . Command
2020-07-08 17:06:44 +00:00
2020-07-16 19:24:30 +00:00
// CLI flags
2020-08-20 19:17:18 +00:00
configPath string
downwardAPIPath string
2020-07-08 17:06:44 +00:00
}
2020-07-23 15:05:21 +00:00
// This is ignored for now because we turn off etcd storage below, but this is
// the right prefix in case we turn it back on.
2020-09-18 22:15:04 +00:00
const defaultEtcdPathPrefix = "/registry/" + loginv1alpha1 . GroupName
2020-07-23 15:05:21 +00:00
2020-07-08 17:06:44 +00:00
// New constructs a new App with command line args, stdout and stderr.
2020-07-23 15:05:21 +00:00
func New ( ctx context . Context , args [ ] string , stdout , stderr io . Writer ) * App {
2020-08-07 21:49:04 +00:00
app := & App { }
app . addServerCommand ( ctx , args , stdout , stderr )
return app
}
// Run the server.
2020-08-11 01:53:53 +00:00
func ( a * App ) Run ( ) error {
return a . cmd . Execute ( )
2020-08-07 21:49:04 +00:00
}
2020-07-08 17:06:44 +00:00
2020-08-07 21:49:04 +00:00
// Create the server command and save it into the App.
2020-08-11 01:53:53 +00:00
func ( a * App ) addServerCommand ( ctx context . Context , args [ ] string , stdout , stderr io . Writer ) {
2020-07-08 17:06:44 +00:00
cmd := & cobra . Command {
2020-09-12 01:15:24 +00:00
Use : "pinniped-server" ,
Long : here . Doc ( `
pinniped - server provides a generic API for mapping an external
credential from somewhere to an internal credential to be used for
authenticating to the Kubernetes API . ` ) ,
2020-08-11 01:53:53 +00:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error { return a . runServer ( ctx ) } ,
2020-07-08 17:06:44 +00:00
Args : cobra . NoArgs ,
}
cmd . SetArgs ( args )
cmd . SetOut ( stdout )
cmd . SetErr ( stderr )
2020-08-11 01:53:53 +00:00
addCommandlineFlagsToCommand ( cmd , a )
2020-07-08 17:06:44 +00:00
2020-08-11 01:53:53 +00:00
a . cmd = cmd
2020-08-07 21:49:04 +00:00
}
// Define the app's commandline flags.
func addCommandlineFlagsToCommand ( cmd * cobra . Command , app * App ) {
2020-07-08 17:06:44 +00:00
cmd . Flags ( ) . StringVarP (
2020-08-07 21:49:04 +00:00
& app . configPath ,
2020-07-08 17:06:44 +00:00
"config" ,
"c" ,
2020-08-20 17:54:15 +00:00
"pinniped.yaml" ,
2020-07-08 17:06:44 +00:00
"path to configuration file" ,
)
2020-07-16 19:24:30 +00:00
cmd . Flags ( ) . StringVar (
2020-08-07 21:49:04 +00:00
& app . downwardAPIPath ,
2020-07-16 19:24:30 +00:00
"downward-api-path" ,
"/etc/podinfo" ,
"path to Downward API volume mount" ,
)
2020-07-08 17:06:44 +00:00
}
2020-08-07 21:49:04 +00:00
// Boot the aggregated API server, which will in turn boot the controllers.
2020-08-11 01:53:53 +00:00
func ( a * App ) runServer ( ctx context . Context ) error {
2020-08-07 21:49:04 +00:00
// Read the server config file.
2020-08-11 01:53:53 +00:00
cfg , err := config . FromPath ( a . configPath )
2020-07-14 15:50:14 +00:00
if err != nil {
return fmt . Errorf ( "could not load config: %w" , err )
}
2020-08-25 01:07:34 +00:00
// Discover in which namespace we are installed.
podInfo , err := downward . Load ( a . downwardAPIPath )
if err != nil {
return fmt . Errorf ( "could not read pod metadata: %w" , err )
}
serverInstallationNamespace := podInfo . Namespace
2020-07-27 12:55:33 +00:00
// Load the Kubernetes cluster signing CA.
2020-09-21 18:16:14 +00:00
kubeCertAgentTemplate , kubeCertAgentLabelSelector := createKubeCertAgentTemplate (
& cfg . KubeCertAgentConfig ,
2020-09-22 00:15:36 +00:00
serverInstallationNamespace ,
2020-09-21 18:16:14 +00:00
)
2020-09-23 00:45:20 +00:00
// TODO replace this with our new controller
2020-09-21 18:16:14 +00:00
k8sClusterCA , shutdownCA , err := getClusterCASigner (
ctx ,
serverInstallationNamespace ,
cfg . NamesConfig . CredentialIssuerConfig ,
kubeCertAgentLabelSelector ,
)
2020-07-27 12:55:33 +00:00
if err != nil {
2020-08-19 18:21:07 +00:00
return err
2020-07-27 12:55:33 +00:00
}
2020-08-19 18:21:07 +00:00
defer shutdownCA ( )
2020-07-27 12:55:33 +00:00
2020-09-14 15:47:16 +00:00
// Initialize the cache of active identity providers.
idpCache := idpcache . New ( )
2020-07-14 15:50:14 +00:00
2020-08-09 17:04:05 +00:00
// This cert provider will provide certs to the API server and will
// be mutated by a controller to keep the certs up to date with what
// is stored in a k8s Secret. Therefore it also effectively acting as
// an in-memory cache of what is stored in the k8s Secret, helping to
// keep incoming requests fast.
2020-08-11 01:53:53 +00:00
dynamicCertProvider := provider . NewDynamicTLSServingCertProvider ( )
2020-07-13 19:30:16 +00:00
2020-08-07 21:49:04 +00:00
// Prepare to start the controllers, but defer actually starting them until the
// post start hook of the aggregated API server.
2020-08-09 17:04:05 +00:00
startControllersFunc , err := controllermanager . PrepareControllers (
2020-09-21 18:16:14 +00:00
& controllermanager . Config {
2020-09-23 00:45:20 +00:00
ServerInstallationNamespace : serverInstallationNamespace ,
NamesConfig : & cfg . NamesConfig ,
DiscoveryURLOverride : cfg . DiscoveryInfo . URL ,
DynamicCertProvider : dynamicCertProvider ,
//KubeAPISigningCertProvider: nil, // TODO pass this as a NewDynamicTLSServingCertProvider(), so it can be passed into the new controller
2020-09-21 18:16:14 +00:00
ServingCertDuration : time . Duration ( * cfg . APIConfig . ServingCertificateConfig . DurationSeconds ) * time . Second ,
ServingCertRenewBefore : time . Duration ( * cfg . APIConfig . ServingCertificateConfig . RenewBeforeSeconds ) * time . Second ,
IDPCache : idpCache ,
KubeCertAgentTemplate : kubeCertAgentTemplate ,
KubeCertAgentCertPathAnnotation : kubeCertAgentCertPathAnnotationKey ,
KubeCertAgentKeyPathAnnotation : kubeCertAgentKeyPathAnnotationKey ,
} ,
2020-08-03 14:17:11 +00:00
)
2020-08-07 21:49:04 +00:00
if err != nil {
return fmt . Errorf ( "could not prepare controllers: %w" , err )
}
// Get the aggregated API server config.
aggregatedAPIServerConfig , err := getAggregatedAPIServerConfig (
2020-08-09 17:04:05 +00:00
dynamicCertProvider ,
2020-09-14 15:47:16 +00:00
idpCache ,
2020-09-23 00:45:20 +00:00
k8sClusterCA , // TODO pass the same instance of DynamicTLSServingCertProvider as above, but wrapped into a new type that implements credentialrequest.CertIssuer, which should return ErrIncapableOfIssuingCertificates until the certs are available
2020-08-07 21:49:04 +00:00
startControllersFunc ,
2020-07-31 16:08:07 +00:00
)
2020-07-23 15:05:21 +00:00
if err != nil {
2020-08-07 21:49:04 +00:00
return fmt . Errorf ( "could not configure aggregated API server: %w" , err )
2020-07-13 19:30:16 +00:00
}
2020-08-07 21:49:04 +00:00
// Complete the aggregated API server config and make a server instance.
server , err := aggregatedAPIServerConfig . Complete ( ) . New ( )
2020-07-23 15:05:21 +00:00
if err != nil {
2020-08-07 21:49:04 +00:00
return fmt . Errorf ( "could not create aggregated API server: %w" , err )
2020-07-23 15:05:21 +00:00
}
2020-07-14 15:50:14 +00:00
2020-08-07 21:49:04 +00:00
// Run the server. Its post-start hook will start the controllers.
2020-07-23 15:05:21 +00:00
return server . GenericAPIServer . PrepareRun ( ) . Run ( ctx . Done ( ) )
2020-07-13 19:30:16 +00:00
}
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
func getClusterCASigner (
ctx context . Context , serverInstallationNamespace string ,
credentialIssuerConfigResourceName string ,
2020-09-21 18:16:14 +00:00
kubeCertAgentLabelSelector string ,
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
) ( credentialrequest . CertIssuer , kubecertauthority . ShutdownFunc , error ) {
2020-08-19 18:21:07 +00:00
// Load the Kubernetes client configuration.
kubeConfig , err := restclient . InClusterConfig ( )
if err != nil {
return nil , nil , fmt . Errorf ( "could not load in-cluster configuration: %w" , err )
}
// Connect to the core Kubernetes API.
kubeClient , err := kubernetes . NewForConfig ( kubeConfig )
if err != nil {
return nil , nil , fmt . Errorf ( "could not initialize Kubernetes client: %w" , err )
}
2020-08-25 01:07:34 +00:00
// Connect to the pinniped API.
pinnipedClient , err := pinnipedclientset . NewForConfig ( kubeConfig )
if err != nil {
return nil , nil , fmt . Errorf ( "could not initialize pinniped client: %w" , err )
}
2020-08-19 18:21:07 +00:00
// Make a clock tick that triggers a periodic refresh.
ticker := time . NewTicker ( 5 * time . Minute )
// Make a CA which uses the Kubernetes cluster API server's signing certs.
2020-09-21 18:16:14 +00:00
kubeCertAgentInfo := kubecertauthority . AgentInfo {
Namespace : "kube-system" ,
LabelSelector : kubeCertAgentLabelSelector ,
CertPathAnnotation : kubeCertAgentCertPathAnnotationKey ,
KeyPathAnnotation : kubeCertAgentKeyPathAnnotationKey ,
}
2020-08-26 01:22:53 +00:00
k8sClusterCA , shutdownCA := kubecertauthority . New (
2020-09-21 18:16:14 +00:00
& kubeCertAgentInfo ,
2020-08-19 18:21:07 +00:00
kubeClient ,
kubecertauthority . NewPodCommandExecutor ( kubeConfig , kubeClient ) ,
ticker . C ,
2020-08-26 01:22:53 +00:00
func ( ) { // success callback
err = issuerconfig . CreateOrUpdateCredentialIssuerConfig (
ctx ,
serverInstallationNamespace ,
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
credentialIssuerConfigResourceName ,
2020-08-26 01:22:53 +00:00
pinnipedClient ,
2020-09-18 21:38:45 +00:00
func ( configToUpdate * configv1alpha1 . CredentialIssuerConfig ) {
configToUpdate . Status . Strategies = [ ] configv1alpha1 . CredentialIssuerConfigStrategy {
2020-08-26 01:22:53 +00:00
{
2020-09-18 21:38:45 +00:00
Type : configv1alpha1 . KubeClusterSigningCertificateStrategyType ,
Status : configv1alpha1 . SuccessStrategyStatus ,
Reason : configv1alpha1 . FetchedKeyStrategyReason ,
2020-08-26 01:22:53 +00:00
Message : "Key was fetched successfully" ,
LastUpdateTime : metav1 . Now ( ) ,
} ,
}
} ,
)
if err != nil {
klog . Errorf ( "error performing create or update on CredentialIssuerConfig to add strategy success: %s" , err . Error ( ) )
}
} ,
func ( err error ) { // error callback
if updateErr := issuerconfig . CreateOrUpdateCredentialIssuerConfig (
ctx ,
serverInstallationNamespace ,
Rename many of resources that are created in Kubernetes by Pinniped
New resource naming conventions:
- Do not repeat the Kind in the name,
e.g. do not call it foo-cluster-role-binding, just call it foo
- Names will generally start with a prefix to identify our component,
so when a user lists all objects of that kind, they can tell to which
component it is related,
e.g. `kubectl get configmaps` would list one named "pinniped-config"
- It should be possible for an operator to make the word "pinniped"
mostly disappear if they choose, by specifying the app_name in
values.yaml, to the extent that is practical (but not from APIService
names because those are hardcoded in golang)
- Each role/clusterrole and its corresponding binding have the same name
- Pinniped resource names that must be known by the server golang code
are passed to the code at run time via ConfigMap, rather than
hardcoded in the golang code. This also allows them to be prepended
with the app_name from values.yaml while creating the ConfigMap.
- Since the CLI `get-kubeconfig` command cannot guess the name of the
CredentialIssuerConfig resource in advance anymore, it lists all
CredentialIssuerConfig in the app's namespace and returns an error
if there is not exactly one found, and then uses that one regardless
of its name
2020-09-18 22:56:50 +00:00
credentialIssuerConfigResourceName ,
2020-08-26 01:22:53 +00:00
pinnipedClient ,
2020-09-18 21:38:45 +00:00
func ( configToUpdate * configv1alpha1 . CredentialIssuerConfig ) {
configToUpdate . Status . Strategies = [ ] configv1alpha1 . CredentialIssuerConfigStrategy {
2020-08-26 01:22:53 +00:00
{
2020-09-18 21:38:45 +00:00
Type : configv1alpha1 . KubeClusterSigningCertificateStrategyType ,
Status : configv1alpha1 . ErrorStrategyStatus ,
Reason : configv1alpha1 . CouldNotFetchKeyStrategyReason ,
2020-08-26 01:22:53 +00:00
Message : err . Error ( ) ,
LastUpdateTime : metav1 . Now ( ) ,
} ,
}
2020-08-25 01:07:34 +00:00
} ,
2020-08-26 01:22:53 +00:00
) ; updateErr != nil {
klog . Errorf ( "error performing create or update on CredentialIssuerConfig to add strategy error: %s" , updateErr . Error ( ) )
2020-08-25 01:07:34 +00:00
}
} ,
)
2020-08-19 18:21:07 +00:00
return k8sClusterCA , func ( ) { shutdownCA ( ) ; ticker . Stop ( ) } , nil
}
2020-08-07 21:49:04 +00:00
// Create a configuration for the aggregated API server.
func getAggregatedAPIServerConfig (
2020-08-11 01:53:53 +00:00
dynamicCertProvider provider . DynamicTLSServingCertProvider ,
2020-09-21 16:37:54 +00:00
authenticator credentialrequest . TokenCredentialRequestAuthenticator ,
issuer credentialrequest . CertIssuer ,
2020-07-31 16:08:07 +00:00
startControllersPostStartHook func ( context . Context ) ,
) ( * apiserver . Config , error ) {
2020-08-07 21:49:04 +00:00
recommendedOptions := genericoptions . NewRecommendedOptions (
defaultEtcdPathPrefix ,
2020-09-18 22:15:04 +00:00
apiserver . Codecs . LegacyCodec ( loginv1alpha1 . SchemeGroupVersion ) ,
2020-08-07 21:49:04 +00:00
// TODO we should check to see if all the other default settings are acceptable for us
)
recommendedOptions . Etcd = nil // turn off etcd storage because we don't need it yet
2020-08-09 17:04:05 +00:00
recommendedOptions . SecureServing . ServerCert . GeneratedCert = dynamicCertProvider
2020-07-13 19:30:16 +00:00
2020-07-23 15:05:21 +00:00
serverConfig := genericapiserver . NewRecommendedConfig ( apiserver . Codecs )
2020-08-09 17:04:05 +00:00
// Note that among other things, this ApplyTo() function copies
// `recommendedOptions.SecureServing.ServerCert.GeneratedCert` into
// `serverConfig.SecureServing.Cert` thus making `dynamicCertProvider`
// the cert provider for the running server. The provider will be called
// by the API machinery periodically. When the provider returns nil certs,
// the API server will return "the server is currently unable to
// handle the request" error responses for all incoming requests.
// If the provider later starts returning certs, then the API server
// will use them to handle the incoming requests successfully.
2020-08-07 21:49:04 +00:00
if err := recommendedOptions . ApplyTo ( serverConfig ) ; err != nil {
2020-07-23 15:05:21 +00:00
return nil , err
}
2020-07-13 19:30:16 +00:00
2020-07-23 15:05:21 +00:00
apiServerConfig := & apiserver . Config {
GenericConfig : serverConfig ,
ExtraConfig : apiserver . ExtraConfig {
2020-09-21 16:37:54 +00:00
Authenticator : authenticator ,
Issuer : issuer ,
2020-07-31 16:08:07 +00:00
StartControllersPostStartHook : startControllersPostStartHook ,
2020-07-23 15:05:21 +00:00
} ,
}
return apiServerConfig , nil
2020-07-13 19:30:16 +00:00
}
2020-09-21 18:16:14 +00:00
2020-09-22 00:15:36 +00:00
func createKubeCertAgentTemplate ( cfg * configapi . KubeCertAgentSpec , serverInstallationNamespace string ) ( * corev1 . Pod , string ) {
2020-09-21 18:16:14 +00:00
terminateImmediately := int64 ( 0 )
pod := & corev1 . Pod {
ObjectMeta : metav1 . ObjectMeta {
2020-09-22 00:15:36 +00:00
Name : * cfg . NamePrefix ,
Namespace : serverInstallationNamespace , // create the agent pods in the same namespace where Pinniped is installed
2020-09-21 18:16:14 +00:00
Labels : map [ string ] string {
kubeCertAgentLabelKey : "" ,
} ,
} ,
Spec : corev1 . PodSpec {
TerminationGracePeriodSeconds : & terminateImmediately ,
Containers : [ ] corev1 . Container {
{
Name : "sleeper" ,
Image : * cfg . Image ,
Command : [ ] string { "/bin/sleep" , "infinity" } ,
Resources : corev1 . ResourceRequirements {
Limits : corev1 . ResourceList {
corev1 . ResourceMemory : resource . MustParse ( "16Mi" ) ,
corev1 . ResourceCPU : resource . MustParse ( "10m" ) ,
} ,
Requests : corev1 . ResourceList {
corev1 . ResourceMemory : resource . MustParse ( "16Mi" ) ,
corev1 . ResourceCPU : resource . MustParse ( "10m" ) ,
} ,
} ,
} ,
} ,
} ,
}
labelSelector := kubeCertAgentLabelKey + "="
return pod , labelSelector
}