Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions cmd/crossplane/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package core
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -61,7 +62,6 @@ import (
"github.com/crossplane/crossplane/v2/internal/controller/protection"
"github.com/crossplane/crossplane/v2/internal/engine"
"github.com/crossplane/crossplane/v2/internal/features"
"github.com/crossplane/crossplane/v2/internal/initializer"
"github.com/crossplane/crossplane/v2/internal/metrics"
"github.com/crossplane/crossplane/v2/internal/protection/usage"
"github.com/crossplane/crossplane/v2/internal/transport"
Expand Down Expand Up @@ -101,7 +101,8 @@ type startCommand struct {

XpkgCacheDir string `aliases:"cache-dir" default:"/cache/xpkg" env:"XPKG_CACHE_DIR,CACHE_DIR" help:"Directory used for caching package images." short:"c"`

PackageRuntime string `default:"Deployment" env:"PACKAGE_RUNTIME" help:"The package runtime to use for packages with a runtime (e.g. Providers and Functions)" placeholder:"runtime | runtime1=package1;runtime2=package2"`
PackageRuntime string `default:"Deployment" env:"PACKAGE_RUNTIME" help:"The package runtime to use for packages with a runtime (e.g. Providers and Functions)" placeholder:"runtime | runtime1=package1;runtime2=package2"`
FunctionEndpointSuffix string `env:"FUNCTION_ENDPOINT_SUFFIX" help:"DNS suffix appended to function service endpoints."`

SyncInterval time.Duration `default:"1h" help:"How often all resources will be double-checked for drift from the desired state." short:"s"`
PollInterval time.Duration `default:"1m" help:"How often individual resources will be checked for drift from the desired state."`
Expand All @@ -123,6 +124,15 @@ type startCommand struct {
TLSClientSecretName string `env:"TLS_CLIENT_SECRET_NAME" help:"The name of the TLS Secret that will be store Crossplane's client certificate."`
TLSClientCertsDir string `env:"TLS_CLIENT_CERTS_DIR" help:"The path of the folder which will store TLS client certificate of Crossplane."`

TLSFunctionServerCAPath string `env:"TLS_FUNCTION_SERVER_CA_PATH" help:"Path to CA certificate used to verify function server TLS certificates."`

TLSClientCACertFileName string `default:"ca.crt" env:"TLS_CLIENT_CA_CERT_FILENAME" help:"The filename of the CA certificate in the TLS client certs directory."`
TLSClientCertFileName string `default:"tls.crt" env:"TLS_CLIENT_CERT_FILENAME" help:"The filename of the client certificate in the TLS client certs directory."`
TLSClientKeyFileName string `default:"tls.key" env:"TLS_CLIENT_KEY_FILENAME" help:"The filename of the client private key in the TLS client certs directory."`

TLSServerCertFileName string `default:"tls.crt" env:"TLS_SERVER_CERT_FILENAME" help:"The filename of the client certificate in the TLS client certs directory."`
TLSServerKeyFileName string `default:"tls.key" env:"TLS_SERVER_KEY_FILENAME" help:"The filename of the client private key in the TLS client certs directory."`

EnableDependencyVersionUpgrades bool `group:"Alpha Features:" help:"Enable support for upgrading dependency versions when the parent package is updated."`
EnableDependencyVersionDowngrades bool `group:"Alpha Features:" help:"Enable support for upgrading and downgrading dependency versions when a dependent package is updated."`
EnableSignatureVerification bool `group:"Alpha Features:" help:"Enable support for package signature verification via ImageConfig API."`
Expand Down Expand Up @@ -188,7 +198,9 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
SyncPeriod: &c.SyncInterval,
},
WebhookServer: webhook.NewServer(webhook.Options{
CertDir: c.TLSServerCertsDir,
CertDir: c.TLSServerCertsDir,
CertName: c.TLSServerCertFileName,
KeyName: c.TLSServerKeyFileName,
TLSOpts: []func(*tls.Config){
func(t *tls.Config) {
t.MinVersion = tls.VersionTLS13
Expand Down Expand Up @@ -254,14 +266,26 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
}

clienttls, err := certificates.LoadMTLSConfig(
filepath.Join(c.TLSClientCertsDir, initializer.SecretKeyCACert),
filepath.Join(c.TLSClientCertsDir, corev1.TLSCertKey),
filepath.Join(c.TLSClientCertsDir, corev1.TLSPrivateKeyKey),
filepath.Join(c.TLSClientCertsDir, c.TLSClientCACertFileName),
filepath.Join(c.TLSClientCertsDir, c.TLSClientCertFileName),
filepath.Join(c.TLSClientCertsDir, c.TLSClientKeyFileName),
false)
if err != nil {
return errors.Wrap(err, "cannot load client TLS certificates")
}

if c.TLSFunctionServerCAPath != "" {
ca, err := os.ReadFile(c.TLSFunctionServerCAPath)
if err != nil {
return errors.Wrap(err, "cannot read function server CA certificate")
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(ca) {
return errors.New("cannot parse function server CA certificate")
}
clienttls.RootCAs = pool
}

pfrm := xfn.NewPrometheusMetrics()
metrics.Registry.MustRegister(pfrm)

Expand Down Expand Up @@ -499,6 +523,7 @@ func (c *startCommand) Run(s *runtime.Scheme, log logging.Logger) error { //noli
FetcherOptions: []xpkg.FetcherOpt{xpkg.WithUserAgent(c.UserAgent)},
PackageRuntime: pr,
MaxConcurrentPackageEstablishers: c.MaxConcurrentPackageEstablishers,
FunctionEndpointSuffix: c.FunctionEndpointSuffix,
}

// We need to set the TUF_ROOT environment variable so that the TUF client
Expand Down
28 changes: 21 additions & 7 deletions cmd/crossplane/core/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package core
import (
"context"
"fmt"
"path/filepath"

admv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -53,6 +54,9 @@ type initCommand struct {
TLSCASecretName string `env:"TLS_CA_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS CA certificate."`
TLSServerSecretName string `env:"TLS_SERVER_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS server certificates."`
TLSClientSecretName string `env:"TLS_CLIENT_SECRET_NAME" help:"The name of the Secret that the initializer will fill with TLS client certificates."`

WebhookTLSCertDir string `env:"WEBHOOK_TLS_CERT_DIR" help:"Directory containing TLS certificates for webhooks. When set, certificates are read from files instead of Secrets."`
WebhookTLSCACert string `default:"ca.crt" env:"WEBHOOK_TLS_CA_CERT" help:"Filename of the CA certificate within the TLS cert directory."`
}

// Run starts the initialization process.
Expand Down Expand Up @@ -91,18 +95,28 @@ func (c *initCommand) Run(s *runtime.Scheme, log logging.Logger) error {
),
)

nn := types.NamespacedName{
Name: c.TLSServerSecretName,
Namespace: c.Namespace,
}
svc := admv1.ServiceReference{
Name: c.WebhookServiceName,
Namespace: c.WebhookServiceNamespace,
Port: &c.WebhookServicePort,
}
steps = append(steps,
initializer.NewCoreCRDs(c.CRDsPath, s, initializer.WithWebhookTLSSecretRef(nn)),
initializer.NewWebhookConfigurations(c.WebhookConfigurationsPath, s, nn, svc))

var caProvider initializer.WebhookCAProvider
if c.WebhookTLSCertDir != "" {
caProvider = &initializer.FileCAProvider{Path: filepath.Join(c.WebhookTLSCertDir, c.WebhookTLSCACert)}
steps = append(steps,
initializer.NewCoreCRDs(c.CRDsPath, s),
initializer.NewWebhookConfigurations(c.WebhookConfigurationsPath, s, caProvider, svc))
} else {
nn := types.NamespacedName{
Name: c.TLSServerSecretName,
Namespace: c.Namespace,
}
caProvider = &initializer.SecretCAProvider{SecretRef: nn}
steps = append(steps,
initializer.NewCoreCRDs(c.CRDsPath, s, initializer.WithWebhookTLSSecretRef(nn)),
initializer.NewWebhookConfigurations(c.WebhookConfigurationsPath, s, caProvider, svc))
}
} else {
log.Info("Warning: Webhooks are disabled, so deprecated ValidatingWebhookConfigurations will not be automatically deleted.")
steps = append(steps,
Expand Down
2 changes: 2 additions & 0 deletions internal/controller/pkg/controller/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ type Options struct {
// MaxConcurrentPackageEstablishers is the maximum number of goroutines to use
// for establishing Providers, Configurations and Functions.
MaxConcurrentPackageEstablishers int

FunctionEndpointSuffix string
}
2 changes: 1 addition & 1 deletion internal/controller/pkg/runtime/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func SetupFunctionRevision(mgr ctrl.Manager, o controller.Options) error {
WithRecorder(event.NewAPIRecorder(mgr.GetEventRecorderFor(name), o.EventFilterFunctions...)),
WithNamespace(o.Namespace),
WithServiceAccount(o.ServiceAccount),
WithRuntimeHooks(NewFunctionHooks(mgr.GetClient())),
WithRuntimeHooks(NewFunctionHooks(mgr.GetClient(), o.FunctionEndpointSuffix)),
WithFeatureFlags(o.Features),
WithConfigStore(xpkg.NewImageConfigStore(mgr.GetClient(), o.Namespace)),
)
Expand Down
19 changes: 15 additions & 4 deletions internal/controller/pkg/runtime/runtime_function.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ const (

// FunctionHooks performs runtime operations for function packages.
type FunctionHooks struct {
client resource.ClientApplicator
client resource.ClientApplicator
endpointSuffix string
}

// NewFunctionHooks returns a new FunctionHooks.
func NewFunctionHooks(client client.Client) *FunctionHooks {
func NewFunctionHooks(client client.Client, endpointSuffix string) *FunctionHooks {
return &FunctionHooks{
client: resource.ClientApplicator{
Client: client,
Applicator: resource.NewAPIPatchingApplicator(client),
},
endpointSuffix: endpointSuffix,
}
}

Expand Down Expand Up @@ -101,15 +103,24 @@ func (h *FunctionHooks) Pre(ctx context.Context, pr v1.PackageRevisionWithRuntim
return errors.Errorf("cannot apply function package hooks to %T", pr)
}

fRev.Status.Endpoint = fmt.Sprintf(ServiceEndpointFmt, svc.Name, svc.Namespace, GRPCPort)
if h.endpointSuffix != "" {
fRev.Status.Endpoint = fmt.Sprintf(ServiceEndpointFmt, svc.Name, svc.Namespace+"."+h.endpointSuffix, GRPCPort)
} else {
fRev.Status.Endpoint = fmt.Sprintf(ServiceEndpointFmt, svc.Name, svc.Namespace, GRPCPort)
}

secServer := build.TLSServerSecret()
if err := h.client.Applicator.Apply(ctx, secServer); err != nil {
return errors.Wrap(err, errApplyFunctionSecret)
}

dnsNames := initializer.DNSNamesForService(svc.Name, svc.Namespace)
if h.endpointSuffix != "" {
dnsNames = append(dnsNames, svc.Name+"."+svc.Namespace+"."+h.endpointSuffix)
}

if err := initializer.NewTLSCertificateGenerator(secServer.Namespace, initializer.RootCACertSecretName,
initializer.TLSCertificateGeneratorWithServerSecretName(secServer.GetName(), initializer.DNSNamesForService(svc.Name, svc.Namespace)),
initializer.TLSCertificateGeneratorWithServerSecretName(secServer.GetName(), dnsNames),
initializer.TLSCertificateGeneratorWithOwner([]metav1.OwnerReference{meta.AsController(meta.TypedReferenceTo(pr, pr.GetObjectKind().GroupVersionKind()))})).Run(ctx, h.client.Client); err != nil {
return errors.Wrapf(err, "cannot generate TLS certificates for %q", pr.GetLabels()[v1.LabelParentPackage])
}
Expand Down
75 changes: 68 additions & 7 deletions internal/controller/pkg/runtime/runtime_function_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ import (

func TestFunctionPreHook(t *testing.T) {
type args struct {
client client.Client
pkg runtime.Object
rev v1.PackageRevisionWithRuntime
manifests ManifestBuilder
client client.Client
pkg runtime.Object
rev v1.PackageRevisionWithRuntime
manifests ManifestBuilder
endpointSuffix string
}

type want struct {
Expand Down Expand Up @@ -115,11 +116,71 @@ func TestFunctionPreHook(t *testing.T) {
},
},
},
"SuccessWithEndpointSuffix": {
reason: "Successful run of pre hook with endpoint suffix.",
args: args{
endpointSuffix: "suffix.example.com",
pkg: &pkgmetav1.Function{
Spec: pkgmetav1.FunctionSpec{},
},
rev: &v1.FunctionRevision{
Spec: v1.FunctionRevisionSpec{
PackageRevisionSpec: v1.PackageRevisionSpec{
DesiredState: v1.PackageRevisionActive,
},
PackageRevisionRuntimeSpec: v1.PackageRevisionRuntimeSpec{
TLSServerSecretName: ptr.To("some-server-secret"),
},
},
},
manifests: &MockManifestBuilder{
ServiceFn: func(_ ...ServiceOverride) *corev1.Service {
return &corev1.Service{}
},
TLSServerSecretFn: func() *corev1.Secret {
return &corev1.Secret{}
},
},
client: &test.MockClient{
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
if svc, ok := obj.(*corev1.Service); ok {
svc.Name = "some-service"
svc.Namespace = "some-namespace"
}
return nil
},
MockPatch: func(_ context.Context, _ client.Object, _ client.Patch, _ ...client.PatchOption) error {
return nil
},
MockUpdate: func(_ context.Context, _ client.Object, _ ...client.UpdateOption) error {
return nil
},
},
},
want: want{
rev: &v1.FunctionRevision{
Spec: v1.FunctionRevisionSpec{
PackageRevisionSpec: v1.PackageRevisionSpec{
DesiredState: v1.PackageRevisionActive,
},
PackageRevisionRuntimeSpec: v1.PackageRevisionRuntimeSpec{
TLSServerSecretName: ptr.To("some-server-secret"),
},
},
Status: v1.FunctionRevisionStatus{
Endpoint: fmt.Sprintf(ServiceEndpointFmt, "some-service", "some-namespace.suffix.example.com", revision.ServicePort),
PackageRevisionRuntimeStatus: v1.PackageRevisionRuntimeStatus{
TLSServerSecretName: ptr.To("some-server-secret"),
},
},
},
},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
h := NewFunctionHooks(tc.args.client)
h := NewFunctionHooks(tc.args.client, tc.args.endpointSuffix)

err := h.Pre(context.TODO(), tc.args.rev, tc.args.manifests)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
Expand Down Expand Up @@ -584,7 +645,7 @@ func TestFunctionPostHook(t *testing.T) {

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
h := NewFunctionHooks(tc.args.client)
h := NewFunctionHooks(tc.args.client, "")

err := h.Post(context.TODO(), tc.args.rev, tc.args.manifests)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
Expand Down Expand Up @@ -689,7 +750,7 @@ func TestFunctionDeactivateHook(t *testing.T) {

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
h := NewFunctionHooks(tc.args.client)
h := NewFunctionHooks(tc.args.client, "")

err := h.Deactivate(context.TODO(), tc.args.rev, tc.args.manifests)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
Expand Down
8 changes: 8 additions & 0 deletions internal/initializer/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,24 @@ func TLSCertificateGeneratorWithOwner(owner []metav1.OwnerReference) TLSCertific
}

// TLSCertificateGeneratorWithServerSecretName returns an TLSCertificateGeneratorOption that sets server secret name.
// If the secret name is empty, the option is a no-op.
func TLSCertificateGeneratorWithServerSecretName(s string, dnsNames []string) TLSCertificateGeneratorOption {
return func(g *TLSCertificateGenerator) {
if s == "" {
return
}
g.tlsServerSecretName = &s
g.tlsServerDNSNames = dnsNames
}
}

// TLSCertificateGeneratorWithClientSecretName returns an TLSCertificateGeneratorOption that sets client secret name.
// If the secret name is empty, the option is a no-op.
func TLSCertificateGeneratorWithClientSecretName(s string, subjects []string) TLSCertificateGeneratorOption {
return func(g *TLSCertificateGenerator) {
if s == "" {
return
}
g.tlsClientSecretName = &s
g.tlsClientDNSNames = subjects
}
Expand Down
Loading