-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding TLS configuration for fn-runner image registries #139
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -16,9 +16,12 @@ package internal | |||||||||||||||
|
||||||||||||||||
import ( | ||||||||||||||||
"context" | ||||||||||||||||
"crypto/tls" | ||||||||||||||||
"crypto/x509" | ||||||||||||||||
"encoding/json" | ||||||||||||||||
"fmt" | ||||||||||||||||
"net" | ||||||||||||||||
"net/http" | ||||||||||||||||
"os" | ||||||||||||||||
"path/filepath" | ||||||||||||||||
"strconv" | ||||||||||||||||
|
@@ -28,6 +31,7 @@ import ( | |||||||||||||||
|
||||||||||||||||
"github.com/google/go-containerregistry/pkg/authn" | ||||||||||||||||
"github.com/google/go-containerregistry/pkg/name" | ||||||||||||||||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||||||||||||||||
"github.com/google/go-containerregistry/pkg/v1/remote" | ||||||||||||||||
"github.com/nephio-project/porch/func/evaluator" | ||||||||||||||||
util "github.com/nephio-project/porch/pkg/util" | ||||||||||||||||
|
@@ -71,7 +75,20 @@ type podEvaluator struct { | |||||||||||||||
|
||||||||||||||||
var _ Evaluator = &podEvaluator{} | ||||||||||||||||
|
||||||||||||||||
func NewPodEvaluator(namespace, wrapperServerImage string, interval, ttl time.Duration, podTTLConfig string, functionPodTemplateName string, enablePrivateRegistries bool, registryAuthSecretPath string, registryAuthSecretName string) (Evaluator, error) { | ||||||||||||||||
func NewPodEvaluator( | ||||||||||||||||
namespace, | ||||||||||||||||
wrapperServerImage string, | ||||||||||||||||
interval, | ||||||||||||||||
ttl time.Duration, | ||||||||||||||||
podTTLConfig string, | ||||||||||||||||
functionPodTemplateName string, | ||||||||||||||||
enablePrivateRegistries bool, | ||||||||||||||||
registryAuthSecretPath string, | ||||||||||||||||
registryAuthSecretName string, | ||||||||||||||||
enableTlsRegistries bool, | ||||||||||||||||
tlsSecretPath string, | ||||||||||||||||
) (Evaluator, error) { | ||||||||||||||||
|
||||||||||||||||
restCfg, err := config.GetConfig() | ||||||||||||||||
if err != nil { | ||||||||||||||||
return nil, fmt.Errorf("failed to get rest config: %w", err) | ||||||||||||||||
|
@@ -105,6 +122,8 @@ func NewPodEvaluator(namespace, wrapperServerImage string, interval, ttl time.Du | |||||||||||||||
enablePrivateRegistries: enablePrivateRegistries, | ||||||||||||||||
registryAuthSecretPath: registryAuthSecretPath, | ||||||||||||||||
registryAuthSecretName: registryAuthSecretName, | ||||||||||||||||
enableTlsRegistries: enableTlsRegistries, | ||||||||||||||||
tlsSecretPath: tlsSecretPath, | ||||||||||||||||
requestCh: reqCh, | ||||||||||||||||
podReadyCh: readyCh, | ||||||||||||||||
cache: map[string]*podAndGRPCClient{}, | ||||||||||||||||
|
@@ -177,6 +196,9 @@ type podCacheManager struct { | |||||||||||||||
registryAuthSecretPath string | ||||||||||||||||
registryAuthSecretName string | ||||||||||||||||
|
||||||||||||||||
enableTlsRegistries bool | ||||||||||||||||
tlsSecretPath string | ||||||||||||||||
|
||||||||||||||||
// requestCh is a receive-only channel to receive | ||||||||||||||||
requestCh <-chan *clientConnRequest | ||||||||||||||||
// podReadyCh is a channel to receive the information when a pod is ready. | ||||||||||||||||
|
@@ -245,7 +267,7 @@ func (pcm *podCacheManager) warmupCache(podTTLConfig string) error { | |||||||||||||||
|
||||||||||||||||
// We invoke the function with useGenerateName=false so that the pod name is fixed, | ||||||||||||||||
// since we want to ensure only one pod is created for each function. | ||||||||||||||||
pcm.podManager.getFuncEvalPodClient(ctx, fnImage, ttl, false, pcm.enablePrivateRegistries, pcm.registryAuthSecretPath, pcm.registryAuthSecretName) | ||||||||||||||||
pcm.podManager.getFuncEvalPodClient(ctx, fnImage, ttl, false, pcm.enablePrivateRegistries, pcm.registryAuthSecretPath, pcm.registryAuthSecretName, pcm.enableTlsRegistries, pcm.tlsSecretPath) | ||||||||||||||||
klog.Infof("preloaded pod cache for function %v", fnImage) | ||||||||||||||||
}) | ||||||||||||||||
|
||||||||||||||||
|
@@ -313,7 +335,7 @@ func (pcm *podCacheManager) podCacheManager() { | |||||||||||||||
pcm.waitlists[req.image] = append(list, req.grpcClientCh) | ||||||||||||||||
// We invoke the function with useGenerateName=true to avoid potential name collision, since if pod foo is | ||||||||||||||||
// being deleted and we can't use the same name. | ||||||||||||||||
go pcm.podManager.getFuncEvalPodClient(context.Background(), req.image, pcm.podTTL, true, pcm.enablePrivateRegistries, pcm.registryAuthSecretPath, pcm.registryAuthSecretName) | ||||||||||||||||
go pcm.podManager.getFuncEvalPodClient(context.Background(), req.image, pcm.podTTL, true, pcm.enablePrivateRegistries, pcm.registryAuthSecretPath, pcm.registryAuthSecretName, pcm.enableTlsRegistries, pcm.tlsSecretPath) | ||||||||||||||||
case resp := <-pcm.podReadyCh: | ||||||||||||||||
if resp.err != nil { | ||||||||||||||||
klog.Warningf("received error from the pod manager: %v", resp.err) | ||||||||||||||||
|
@@ -445,9 +467,9 @@ type digestAndEntrypoint struct { | |||||||||||||||
// time-to-live period for the pod. If useGenerateName is false, it will try to | ||||||||||||||||
// create a pod with a fixed name. Otherwise, it will create a pod and let the | ||||||||||||||||
// apiserver to generate the name from a template. | ||||||||||||||||
func (pm *podManager) getFuncEvalPodClient(ctx context.Context, image string, ttl time.Duration, useGenerateName bool, enablePrivateRegistries bool, registryAuthSecretPath string, registryAuthSecretName string) { | ||||||||||||||||
func (pm *podManager) getFuncEvalPodClient(ctx context.Context, image string, ttl time.Duration, useGenerateName bool, enablePrivateRegistries bool, registryAuthSecretPath string, registryAuthSecretName string, enableTlsRegistries bool, tlsSecretPath string) { | ||||||||||||||||
c, err := func() (*podAndGRPCClient, error) { | ||||||||||||||||
podKey, err := pm.retrieveOrCreatePod(ctx, image, ttl, useGenerateName, enablePrivateRegistries, registryAuthSecretPath, registryAuthSecretName) | ||||||||||||||||
podKey, err := pm.retrieveOrCreatePod(ctx, image, ttl, useGenerateName, enablePrivateRegistries, registryAuthSecretPath, registryAuthSecretName, enableTlsRegistries, tlsSecretPath) | ||||||||||||||||
if err != nil { | ||||||||||||||||
return nil, err | ||||||||||||||||
} | ||||||||||||||||
|
@@ -539,7 +561,7 @@ type DockerConfig struct { | |||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// imageDigestAndEntrypoint gets the entrypoint of a container image by looking at its metadata. | ||||||||||||||||
func (pm *podManager) imageDigestAndEntrypoint(ctx context.Context, image string, enablePrivateRegistries bool, registryAuthSecretPath string, registryAuthSecretName string) (*digestAndEntrypoint, error) { | ||||||||||||||||
func (pm *podManager) imageDigestAndEntrypoint(ctx context.Context, image string, enablePrivateRegistries bool, registryAuthSecretPath string, registryAuthSecretName string, enableTlsRegistries bool, tlsSecretPath string) (*digestAndEntrypoint, error) { | ||||||||||||||||
start := time.Now() | ||||||||||||||||
defer func() { | ||||||||||||||||
klog.Infof("getting image metadata for %v took %v", image, time.Since(start)) | ||||||||||||||||
|
@@ -569,7 +591,7 @@ func (pm *podManager) imageDigestAndEntrypoint(ctx context.Context, image string | |||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
return pm.getImageMetadata(ctx, ref, auth, image) | ||||||||||||||||
return pm.getImageMetadata(ctx, ref, auth, image, enablePrivateRegistries, enableTlsRegistries, tlsSecretPath) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// ensureCustomAuthSecret ensures that, if an image from a custom registry is requested, the appropriate credentials are passed into a secret for function pods to use when pulling. If the secret does not already exist, it is created. | ||||||||||||||||
|
@@ -597,9 +619,70 @@ func (pm *podManager) getCustomAuth(ref name.Reference, registryAuthSecretPath s | |||||||||||||||
return authn.FromConfig(dockerConfig.Auths[ref.Context().RegistryStr()]), nil | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func loadTLSConfig(caCertPath string) (*tls.Config, error) { | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider moving these new methods to be more in the order they're used in - so:
|
||||||||||||||||
// Read the CA certificate file | ||||||||||||||||
caCert, err := os.ReadFile(caCertPath) | ||||||||||||||||
if err != nil { | ||||||||||||||||
return nil, err | ||||||||||||||||
} | ||||||||||||||||
// Append the CA certificate to the system pool | ||||||||||||||||
caCertPool := x509.NewCertPool() | ||||||||||||||||
if !caCertPool.AppendCertsFromPEM(caCert) { | ||||||||||||||||
return nil, err | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't |
||||||||||||||||
} | ||||||||||||||||
// Create a tls.Config with the CA pool | ||||||||||||||||
tlsConfig := &tls.Config{ | ||||||||||||||||
RootCAs: caCertPool, | ||||||||||||||||
} | ||||||||||||||||
return tlsConfig, nil | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func createTransport(tlsConfig *tls.Config) *http.Transport { | ||||||||||||||||
return &http.Transport{ | ||||||||||||||||
TLSClientConfig: tlsConfig, | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
func getImage(ctx context.Context, ref name.Reference, auth authn.Authenticator, image string, enablePrivateRegistries bool, enableTlsRegistries bool, tlsSecretPath string) (v1.Image, error) { | ||||||||||||||||
if enablePrivateRegistries && !strings.HasPrefix(image, defaultRegistry) && enableTlsRegistries { | ||||||||||||||||
tlsFile := "ca.crt" | ||||||||||||||||
// Check if mounted secret location contains CA file. | ||||||||||||||||
_, err := os.Stat(tlsSecretPath) | ||||||||||||||||
if os.IsNotExist(err) { | ||||||||||||||||
return nil, err | ||||||||||||||||
} | ||||||||||||||||
Comment on lines
+650
to
+653
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Combined check syntax?
Suggested change
|
||||||||||||||||
if _, err := os.Stat(filepath.Join(tlsSecretPath, "ca.crt")); os.IsNotExist(err) { | ||||||||||||||||
klog.Error(err) | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this log be inside the check-for-ca.pem if block, to indicate that we couldn't find a |
||||||||||||||||
if _, err := os.Stat(filepath.Join(tlsSecretPath, "ca.pem")); os.IsNotExist(err) { | ||||||||||||||||
return nil, err | ||||||||||||||||
} | ||||||||||||||||
tlsFile = "ca.pem" | ||||||||||||||||
} | ||||||||||||||||
// Load the custom TLS configuration | ||||||||||||||||
tlsConfig, err := loadTLSConfig(filepath.Join(tlsSecretPath, tlsFile)) | ||||||||||||||||
if err != nil { | ||||||||||||||||
return nil, err | ||||||||||||||||
} | ||||||||||||||||
// Create a custom HTTP transport | ||||||||||||||||
transport := createTransport(tlsConfig) | ||||||||||||||||
|
||||||||||||||||
// Attempt image pull with TLS | ||||||||||||||||
img, tlsErr := remote.Image(ref, remote.WithAuth(auth), remote.WithContext(ctx), remote.WithTransport(transport)) | ||||||||||||||||
if tlsErr != nil { | ||||||||||||||||
// Attempt without TLS | ||||||||||||||||
klog.Errorf("Pulling image %s with the provided TLS Cert has failed with error %v", image, tlsErr) | ||||||||||||||||
klog.Infof("Attempting image pull without TLS") | ||||||||||||||||
return remote.Image(ref, remote.WithAuth(auth), remote.WithContext(ctx)) | ||||||||||||||||
} | ||||||||||||||||
return img, tlsErr | ||||||||||||||||
} else { | ||||||||||||||||
return remote.Image(ref, remote.WithAuth(auth), remote.WithContext(ctx)) | ||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// getImageMetadata retrieves the image digest and entrypoint. | ||||||||||||||||
func (pm *podManager) getImageMetadata(ctx context.Context, ref name.Reference, auth authn.Authenticator, image string) (*digestAndEntrypoint, error) { | ||||||||||||||||
img, err := remote.Image(ref, remote.WithAuth(auth), remote.WithContext(ctx)) | ||||||||||||||||
func (pm *podManager) getImageMetadata(ctx context.Context, ref name.Reference, auth authn.Authenticator, image string, enablePrivateRegistries bool, enableTlsRegistries bool, tlsSecretPath string) (*digestAndEntrypoint, error) { | ||||||||||||||||
img, err := getImage(ctx, ref, auth, image, enablePrivateRegistries, enableTlsRegistries, tlsSecretPath) | ||||||||||||||||
if err != nil { | ||||||||||||||||
return nil, err | ||||||||||||||||
} | ||||||||||||||||
|
@@ -626,14 +709,14 @@ func (pm *podManager) getImageMetadata(ctx context.Context, ref name.Reference, | |||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
// retrieveOrCreatePod retrieves or creates a pod for an image. | ||||||||||||||||
func (pm *podManager) retrieveOrCreatePod(ctx context.Context, image string, ttl time.Duration, useGenerateName bool, enablePrivateRegistries bool, registryAuthSecretPath string, registryAuthSecretName string) (client.ObjectKey, error) { | ||||||||||||||||
func (pm *podManager) retrieveOrCreatePod(ctx context.Context, image string, ttl time.Duration, useGenerateName bool, enablePrivateRegistries bool, registryAuthSecretPath string, registryAuthSecretName string, enableTlsRegistries bool, tlsSecretPath string) (client.ObjectKey, error) { | ||||||||||||||||
var de *digestAndEntrypoint | ||||||||||||||||
var replacePod bool | ||||||||||||||||
var currentPod *corev1.Pod | ||||||||||||||||
var err error | ||||||||||||||||
val, found := pm.imageMetadataCache.Load(image) | ||||||||||||||||
if !found { | ||||||||||||||||
de, err = pm.imageDigestAndEntrypoint(ctx, image, enablePrivateRegistries, registryAuthSecretPath, registryAuthSecretName) | ||||||||||||||||
de, err = pm.imageDigestAndEntrypoint(ctx, image, enablePrivateRegistries, registryAuthSecretPath, registryAuthSecretName, enableTlsRegistries, tlsSecretPath) | ||||||||||||||||
if err != nil { | ||||||||||||||||
return client.ObjectKey{}, fmt.Errorf("unable to get the entrypoint for %v: %w", image, err) | ||||||||||||||||
} | ||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this have a more distinctive alias? Something like