diff --git a/go.mod b/go.mod index 20aad7b197..b0447d5056 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,8 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require sigs.k8s.io/gateway-api v1.0.0 // indirect + require ( cloud.google.com/go/compute v1.24.0 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect @@ -69,6 +71,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cert-manager/cert-manager v1.14.5 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -135,7 +138,7 @@ require ( github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.4 // indirect + github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/golang-lru v0.6.0 // indirect @@ -145,7 +148,7 @@ require ( github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ionos-cloud/sdk-go/v6 v6.1.11 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.17.7 // indirect @@ -180,7 +183,7 @@ require ( github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/scaleway/scaleway-sdk-go v1.0.0-beta.25 // indirect - github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -196,14 +199,14 @@ require ( go.uber.org/goleak v1.3.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.22.0 // indirect golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect golang.org/x/mod v0.16.0 // indirect - golang.org/x/net v0.23.0 // indirect + golang.org/x/net v0.24.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.19.0 // indirect - golang.org/x/term v0.18.0 // indirect + golang.org/x/term v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.19.0 // indirect diff --git a/go.sum b/go.sum index dd71ff5e6f..2d237403ff 100644 --- a/go.sum +++ b/go.sum @@ -100,6 +100,8 @@ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZX github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cert-manager/cert-manager v1.14.5 h1:uuM1O2g2S80nxiH3eW2cZYMGiL2zmDFVdAzg8sibWuc= +github.com/cert-manager/cert-manager v1.14.5/go.mod h1:fmr/cU5jiLxWj69CroDggSOa49RljUK+dU583TaQUXM= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -118,6 +120,7 @@ github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -354,6 +357,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= @@ -391,6 +396,8 @@ github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nu github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY= +github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -585,6 +592,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -678,6 +687,8 @@ golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -755,6 +766,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -845,6 +858,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1060,6 +1075,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.17.3 h1:65QmN7r3FWgTxDMz9fvGnO1kbf2nu+acg9p2R9oYYYk= sigs.k8s.io/controller-runtime v0.17.3/go.mod h1:N0jpP5Lo7lMTF9aL56Z/B2oWBJjey6StQM0jRbKQXtY= +sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs= +sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= diff --git a/internal/autodetect/certmanager/check.go b/internal/autodetect/certmanager/check.go new file mode 100644 index 0000000000..72667d84f0 --- /dev/null +++ b/internal/autodetect/certmanager/check.go @@ -0,0 +1,82 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package certmanager + +import ( + "context" + "fmt" + "os" + + rbacv1 "k8s.io/api/rbac/v1" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/open-telemetry/opentelemetry-operator/internal/rbac" +) + +const ( + SA_ENV_VAR = "SERVICE_ACCOUNT_NAME" + NAMESPACE_ENV_VAR = "NAMESPACE" + NAMESPACE_FILE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" +) + +func getOperatorNamespace() (string, error) { + namespace := os.Getenv(NAMESPACE_ENV_VAR) + if namespace != "" { + return namespace, nil + } + + nsBytes, err := os.ReadFile(NAMESPACE_FILE_PATH) + if err != nil { + return "", err + } + return string(nsBytes), nil +} + +func getOperatorServiceAccount() (string, error) { + sa := os.Getenv(SA_ENV_VAR) + if sa == "" { + return sa, fmt.Errorf("%s env variable not found", SA_ENV_VAR) + } + return sa, nil +} + +// CheckCertManagerPermissions checks if the operator has the needed permissions to manage cert-manager certificates automatically. +// If the RBAC is there, no errors nor warnings are returned. +func CheckCertManagerPermissions(ctx context.Context, reviewer *rbac.Reviewer) (admission.Warnings, error) { + namespace, err := getOperatorNamespace() + if err != nil { + return nil, fmt.Errorf("%s: %w", "not possible to check RBAC rules", err) + } + + serviceAccount, err := getOperatorServiceAccount() + if err != nil { + return nil, fmt.Errorf("%s: %w", "not possible to check RBAC rules", err) + } + + rules := []*rbacv1.PolicyRule{ + { + APIGroups: []string{"cert-manager.io"}, + Resources: []string{"issuers", "certificaterequests", "certificates"}, + Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, + }, + } + + if subjectAccessReviews, err := reviewer.CheckPolicyRules(ctx, serviceAccount, namespace, rules...); err != nil { + return nil, fmt.Errorf("%s: %w", "unable to check rbac rules", err) + } else if allowed, deniedReviews := rbac.AllSubjectAccessReviewsAllowed(subjectAccessReviews); !allowed { + return rbac.WarningsGroupedByResource(deniedReviews), nil + } + return nil, nil +} diff --git a/internal/autodetect/certmanager/operator.go b/internal/autodetect/certmanager/operator.go new file mode 100644 index 0000000000..19ec9baf18 --- /dev/null +++ b/internal/autodetect/certmanager/operator.go @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package certmanager + +// Availability represents that the Cert Manager CRDs are installed and the operator's service account has permissions to manage cert-manager resources. +type Availability int + +const ( + // NotAvailable Cert Manager CRDs or RBAC permissions to manage cert-manager certificates are not available. + NotAvailable Availability = iota + + // Available Cert Manager CRDs and RBAC permissions to manage cert-manager certificates are available. + Available +) + +func (p Availability) String() string { + return [...]string{"NotAvailable", "Available"}[p] +} diff --git a/internal/autodetect/main.go b/internal/autodetect/main.go index 359843dcd0..8dadb962ab 100644 --- a/internal/autodetect/main.go +++ b/internal/autodetect/main.go @@ -22,6 +22,7 @@ import ( "k8s.io/client-go/discovery" "k8s.io/client-go/rest" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" @@ -35,6 +36,7 @@ type AutoDetect interface { OpenShiftRoutesAvailability() (openshift.RoutesAvailability, error) PrometheusCRsAvailability() (prometheus.Availability, error) RBACPermissions(ctx context.Context) (autoRBAC.Availability, error) + CertManagerAvailability(ctx context.Context) (certmanager.Availability, error) } type autoDetect struct { @@ -103,3 +105,33 @@ func (a *autoDetect) RBACPermissions(ctx context.Context) (autoRBAC.Availability return autoRBAC.Available, nil } + +func (a *autoDetect) CertManagerAvailability(ctx context.Context) (certmanager.Availability, error) { + apiList, err := a.dcl.ServerGroups() + if err != nil { + return certmanager.NotAvailable, err + } + + apiGroups := apiList.Groups + certManagerFound := false + for i := 0; i < len(apiGroups); i++ { + if apiGroups[i].Name == "cert-manager.io" { + certManagerFound = true + break + } + } + + if !certManagerFound { + return certmanager.NotAvailable, nil + } + + w, err := certmanager.CheckCertManagerPermissions(ctx, a.reviewer) + if err != nil { + return certmanager.NotAvailable, err + } + if w != nil { + return certmanager.NotAvailable, fmt.Errorf("missing permissions: %s", w) + } + + return certmanager.Available, nil +} diff --git a/internal/config/main.go b/internal/config/main.go index a0dbe533b9..e32164585e 100644 --- a/internal/config/main.go +++ b/internal/config/main.go @@ -17,12 +17,14 @@ package config import ( "context" + "fmt" "time" "github.com/go-logr/logr" logf "sigs.k8s.io/controller-runtime/pkg/log" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" @@ -65,6 +67,7 @@ type Config struct { openshiftRoutesAvailability openshift.RoutesAvailability prometheusCRAvailability prometheus.Availability + certManagerAvailability certmanager.Availability labelsFilter []string annotationsFilter []string } @@ -76,6 +79,7 @@ func New(opts ...Option) Config { prometheusCRAvailability: prometheus.NotAvailable, openshiftRoutesAvailability: openshift.RoutesNotAvailable, createRBACPermissions: autoRBAC.NotAvailable, + certManagerAvailability: certmanager.NotAvailable, collectorConfigMapEntry: defaultCollectorConfigMapEntry, targetAllocatorConfigMapEntry: defaultTargetAllocatorConfigMapEntry, operatorOpAMPBridgeConfigMapEntry: defaultOperatorOpAMPBridgeConfigMapEntry, @@ -108,6 +112,7 @@ func New(opts ...Option) Config { logger: o.logger, openshiftRoutesAvailability: o.openshiftRoutesAvailability, prometheusCRAvailability: o.prometheusCRAvailability, + certManagerAvailability: o.certManagerAvailability, autoInstrumentationJavaImage: o.autoInstrumentationJavaImage, autoInstrumentationNodeJSImage: o.autoInstrumentationNodeJSImage, autoInstrumentationPythonImage: o.autoInstrumentationPythonImage, @@ -145,6 +150,14 @@ func (c *Config) AutoDetect() error { c.createRBACPermissions = rAuto c.logger.V(2).Info("create rbac permissions detected", "availability", rAuto) + cmAvl, err := c.autoDetect.CertManagerAvailability(context.Background()) + if err != nil { + c.logger.V(2).Info("the cert manager crd and permissions are not set for the operator", "reason", err) + fmt.Print(err) + } + c.certManagerAvailability = cmAvl + c.logger.V(2).Info("the cert manager crd and permissions are set for the operator", "availability", cmAvl) + return nil } @@ -233,6 +246,11 @@ func (c *Config) PrometheusCRAvailability() prometheus.Availability { return c.prometheusCRAvailability } +// CertManagerAvailability represents the availability of the Cert-Manager. +func (c *Config) CertManagerAvailability() certmanager.Availability { + return c.certManagerAvailability +} + // AutoInstrumentationJavaImage returns OpenTelemetry Java auto-instrumentation container image. func (c *Config) AutoInstrumentationJavaImage() string { return c.autoInstrumentationJavaImage diff --git a/internal/config/main_test.go b/internal/config/main_test.go index 1f3886f776..1d6e236264 100644 --- a/internal/config/main_test.go +++ b/internal/config/main_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/require" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" @@ -56,6 +57,9 @@ func TestConfigChangesOnAutoDetect(t *testing.T) { RBACPermissionsFunc: func(ctx context.Context) (rbac.Availability, error) { return rbac.Available, nil }, + CertManagerAvailabilityFunc: func(ctx context.Context) (certmanager.Availability, error) { + return certmanager.Available, nil + }, } cfg := config.New( config.WithAutoDetect(mock), @@ -80,6 +84,7 @@ type mockAutoDetect struct { OpenShiftRoutesAvailabilityFunc func() (openshift.RoutesAvailability, error) PrometheusCRsAvailabilityFunc func() (prometheus.Availability, error) RBACPermissionsFunc func(ctx context.Context) (rbac.Availability, error) + CertManagerAvailabilityFunc func(ctx context.Context) (certmanager.Availability, error) } func (m *mockAutoDetect) OpenShiftRoutesAvailability() (openshift.RoutesAvailability, error) { @@ -102,3 +107,10 @@ func (m *mockAutoDetect) RBACPermissions(ctx context.Context) (rbac.Availability } return rbac.NotAvailable, nil } + +func (m *mockAutoDetect) CertManagerAvailability(ctx context.Context) (certmanager.Availability, error) { + if m.CertManagerAvailabilityFunc != nil { + return m.CertManagerAvailabilityFunc(ctx) + } + return certmanager.NotAvailable, nil +} diff --git a/internal/config/options.go b/internal/config/options.go index 7635059413..66e2eee708 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -21,6 +21,7 @@ import ( "github.com/go-logr/logr" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" autoRBAC "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/rbac" @@ -58,6 +59,7 @@ type options struct { operatorOpAMPBridgeImage string openshiftRoutesAvailability openshift.RoutesAvailability prometheusCRAvailability prometheus.Availability + certManagerAvailability certmanager.Availability labelsFilter []string annotationsFilter []string } @@ -208,6 +210,12 @@ func WithRBACPermissions(rAuto autoRBAC.Availability) Option { } } +func WithCertManagerAvailability(cmAvl certmanager.Availability) Option { + return func(o *options) { + o.certManagerAvailability = cmAvl + } +} + func WithLabelFilters(labelFilters []string) Option { return func(o *options) { diff --git a/internal/manifests/mutate.go b/internal/manifests/mutate.go index 9ac2d04fd2..92fcfb860b 100644 --- a/internal/manifests/mutate.go +++ b/internal/manifests/mutate.go @@ -20,6 +20,7 @@ import ( "reflect" "dario.cat/mergo" + cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" routev1 "github.com/openshift/api/route/v1" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" appsv1 "k8s.io/api/apps/v1" @@ -166,6 +167,16 @@ func MutateFuncFor(existing, desired client.Object) controllerutil.MutateFn { wantPr := desired.(*corev1.Secret) mutateSecret(pr, wantPr) + case *cmv1.Certificate: + cert := existing.(*cmv1.Certificate) + wantCert := desired.(*cmv1.Certificate) + mutateCertificate(cert, wantCert) + + case *cmv1.Issuer: + issuer := existing.(*cmv1.Issuer) + wantIssuer := desired.(*cmv1.Issuer) + mutateIssuer(issuer, wantIssuer) + default: t := reflect.TypeOf(existing).String() return fmt.Errorf("missing mutate implementation for resource type: %s", t) @@ -331,6 +342,18 @@ func mutateStatefulSet(existing, desired *appsv1.StatefulSet) error { return nil } +func mutateCertificate(existing, desired *cmv1.Certificate) { + existing.Annotations = desired.Annotations + existing.Labels = desired.Labels + existing.Spec = desired.Spec +} + +func mutateIssuer(existing, desired *cmv1.Issuer) { + existing.Annotations = desired.Annotations + existing.Labels = desired.Labels + existing.Spec = desired.Spec +} + func hasImmutableFieldChange(existing, desired *appsv1.StatefulSet) (bool, string) { if existing.CreationTimestamp.IsZero() { return false, "" diff --git a/internal/manifests/targetallocator/adapters/config_to_prom_config.go b/internal/manifests/targetallocator/adapters/config_to_prom_config.go index e0d7cd38e2..1dd316e375 100644 --- a/internal/manifests/targetallocator/adapters/config_to_prom_config.go +++ b/internal/manifests/targetallocator/adapters/config_to_prom_config.go @@ -23,6 +23,8 @@ import ( "github.com/open-telemetry/opentelemetry-operator/internal/manifests/collector/adapters" ) +type TAOption func(targetAllocatorCfg map[interface{}]interface{}) error + func errorNoComponent(component string) error { return fmt.Errorf("no %s available as part of the configuration", component) } @@ -257,10 +259,32 @@ func AddHTTPSDConfigToPromConfig(prometheus map[interface{}]interface{}, taServi return prometheus, nil } +func WithTLSConfig(caFile, certFile, keyFile string) TAOption { + return func(targetAllocatorCfg map[interface{}]interface{}) error { + if targetAllocatorCfg["tls"] == nil { + targetAllocatorCfg["tls"] = make(map[interface{}]interface{}) + } + + targetAllocatorCfg, ok := targetAllocatorCfg["tls"].(map[interface{}]interface{}) + if !ok { + return errorNotAMap("tls") + } + targetAllocatorCfg["tls"] = make(map[interface{}]interface{}) + tlsCfg, ok := targetAllocatorCfg["tls"].(map[interface{}]interface{}) + if !ok { + return errorNotAMap("tls") + } + tlsCfg["ca_file"] = caFile + tlsCfg["cert_file"] = certFile + tlsCfg["key_file"] = keyFile + return nil + } +} + // AddTAConfigToPromConfig adds or updates the target_allocator configuration in the Prometheus configuration. // If the `EnableTargetAllocatorRewrite` feature flag for the target allocator is enabled, this function // removes the existing scrape_configs from the collector's Prometheus configuration as it's not required. -func AddTAConfigToPromConfig(prometheus map[interface{}]interface{}, taServiceName string) (map[interface{}]interface{}, error) { +func AddTAConfigToPromConfig(prometheus map[interface{}]interface{}, taServiceName string, taOpts ...TAOption) (map[interface{}]interface{}, error) { prometheusConfigProperty, ok := prometheus["config"] if !ok { return nil, errorNoComponent("prometheusConfig") @@ -285,6 +309,13 @@ func AddTAConfigToPromConfig(prometheus map[interface{}]interface{}, taServiceNa targetAllocatorCfg["interval"] = "30s" targetAllocatorCfg["collector_id"] = "${POD_NAME}" + for _, opt := range taOpts { + err := opt(targetAllocatorCfg) + if err != nil { + return nil, err + } + } + // Remove the scrape_configs key from the map delete(prometheusCfg, "scrape_configs") diff --git a/internal/manifests/targetallocator/certificate.go b/internal/manifests/targetallocator/certificate.go new file mode 100644 index 0000000000..8ad3d3cd67 --- /dev/null +++ b/internal/manifests/targetallocator/certificate.go @@ -0,0 +1,113 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package targetallocator + +import ( + "fmt" + + cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/open-telemetry/opentelemetry-operator/internal/manifests" + "github.com/open-telemetry/opentelemetry-operator/internal/manifests/manifestutils" + "github.com/open-telemetry/opentelemetry-operator/internal/naming" +) + +// / CACertificate returns a CA Certificate for the given instance. +func CACertificate(params manifests.Params) *cmv1.Certificate { + name := naming.CACertificate(params.TargetAllocator.Name) + labels := manifestutils.Labels(params.TargetAllocator.ObjectMeta, name, params.TargetAllocator.Spec.Image, ComponentOpenTelemetryTargetAllocator, nil) + + return &cmv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: params.TargetAllocator.Namespace, + Name: name, + Labels: labels, + }, + Spec: cmv1.CertificateSpec{ + IsCA: true, + CommonName: naming.CACertificate(params.TargetAllocator.Name), + Subject: &cmv1.X509Subject{ + OrganizationalUnits: []string{"opentelemetry-operator"}, + }, + SecretName: naming.CACertificate(params.TargetAllocator.Name), + PrivateKey: &cmv1.CertificatePrivateKey{ + Algorithm: "ECDSA", + Size: 256, + }, + IssuerRef: cmmeta.ObjectReference{ + Name: naming.SelfSignedIssuer(params.TargetAllocator.Name), + Kind: "Issuer", + }, + }, + } +} + +// ServingCertificate returns a serving Certificate for the given instance. +func ServingCertificate(params manifests.Params) *cmv1.Certificate { + name := naming.TAServerCertificate(params.TargetAllocator.Name) + labels := manifestutils.Labels(params.TargetAllocator.ObjectMeta, name, params.TargetAllocator.Spec.Image, ComponentOpenTelemetryTargetAllocator, nil) + + return &cmv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: params.TargetAllocator.Namespace, + Name: name, + Labels: labels, + }, + Spec: cmv1.CertificateSpec{ + DNSNames: []string{ + fmt.Sprintf("%s.%s.svc", naming.TAService(params.TargetAllocator.Name), params.TargetAllocator.Namespace), + fmt.Sprintf("%s.%s.svc.cluster.local", naming.TAService(params.TargetAllocator.Name), params.TargetAllocator.Namespace), + }, + IssuerRef: cmmeta.ObjectReference{ + Kind: "Issuer", + Name: naming.CAIssuer(params.TargetAllocator.Name), + }, + SecretName: naming.TAServerCertificate(params.TargetAllocator.Name), + Subject: &cmv1.X509Subject{ + OrganizationalUnits: []string{"opentelemetry-operator"}, + }, + }, + } +} + +// ClientCertificate returns a client Certificate for the given instance. +func ClientCertificate(params manifests.Params) *cmv1.Certificate { + name := naming.TAClientCertificate(params.TargetAllocator.Name) + labels := manifestutils.Labels(params.TargetAllocator.ObjectMeta, name, params.TargetAllocator.Spec.Image, ComponentOpenTelemetryTargetAllocator, nil) + + return &cmv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: params.TargetAllocator.Namespace, + Name: name, + Labels: labels, + }, + Spec: cmv1.CertificateSpec{ + DNSNames: []string{ + fmt.Sprintf("%s.%s.svc", naming.TAService(params.TargetAllocator.Name), params.TargetAllocator.Namespace), + fmt.Sprintf("%s.%s.svc.cluster.local", naming.TAService(params.TargetAllocator.Name), params.TargetAllocator.Namespace), + }, + IssuerRef: cmmeta.ObjectReference{ + Kind: "Issuer", + Name: naming.CAIssuer(params.TargetAllocator.Name), + }, + SecretName: naming.TAClientCertificate(params.TargetAllocator.Name), + Subject: &cmv1.X509Subject{ + OrganizationalUnits: []string{"opentelemetry-operator"}, + }, + }, + } +} diff --git a/internal/manifests/targetallocator/issuer.go b/internal/manifests/targetallocator/issuer.go new file mode 100644 index 0000000000..56c170d2d6 --- /dev/null +++ b/internal/manifests/targetallocator/issuer.go @@ -0,0 +1,63 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package targetallocator + +import ( + cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/open-telemetry/opentelemetry-operator/internal/manifests" + "github.com/open-telemetry/opentelemetry-operator/internal/manifests/manifestutils" + "github.com/open-telemetry/opentelemetry-operator/internal/naming" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SelfSignedIssuer returns a self-signed issuer for the given instance. +func SelfSignedIssuer(params manifests.Params) *cmv1.Issuer { + name := naming.SelfSignedIssuer(params.TargetAllocator.Name) + labels := manifestutils.Labels(params.TargetAllocator.ObjectMeta, name, params.TargetAllocator.Spec.Image, ComponentOpenTelemetryTargetAllocator, nil) + + return &cmv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.TargetAllocator.Namespace, + Labels: labels, + }, + Spec: cmv1.IssuerSpec{ + IssuerConfig: cmv1.IssuerConfig{ + SelfSigned: &cmv1.SelfSignedIssuer{}, + }, + }, + } +} + +// CAIssuer returns a CA issuer for the given instance. +func CAIssuer(params manifests.Params) *cmv1.Issuer { + name := naming.CAIssuer(params.TargetAllocator.Name) + labels := manifestutils.Labels(params.TargetAllocator.ObjectMeta, name, params.TargetAllocator.Spec.Image, ComponentOpenTelemetryTargetAllocator, nil) + + return &cmv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: params.TargetAllocator.Namespace, + Labels: labels, + }, + Spec: cmv1.IssuerSpec{ + IssuerConfig: cmv1.IssuerConfig{ + CA: &cmv1.CAIssuer{ + SecretName: naming.CACertificate(params.TargetAllocator.Name), + }, + }, + }, + } +} diff --git a/internal/manifests/targetallocator/targetallocator.go b/internal/manifests/targetallocator/targetallocator.go index 84705184cf..41797bc5d7 100644 --- a/internal/manifests/targetallocator/targetallocator.go +++ b/internal/manifests/targetallocator/targetallocator.go @@ -17,6 +17,7 @@ package targetallocator import ( "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager" "github.com/open-telemetry/opentelemetry-operator/internal/manifests" "github.com/open-telemetry/opentelemetry-operator/pkg/featuregate" ) @@ -43,6 +44,14 @@ func Build(params manifests.Params) ([]client.Object, error) { resourceFactories = append(resourceFactories, manifests.FactoryWithoutError(ServiceMonitor)) } + if params.Config.CertManagerAvailability() == certmanager.Available { + resourceFactories = append(resourceFactories, manifests.FactoryWithoutError(SelfSignedIssuer)) + resourceFactories = append(resourceFactories, manifests.FactoryWithoutError(CACertificate)) + resourceFactories = append(resourceFactories, manifests.FactoryWithoutError(CAIssuer)) + resourceFactories = append(resourceFactories, manifests.FactoryWithoutError(ServingCertificate)) + resourceFactories = append(resourceFactories, manifests.FactoryWithoutError(ClientCertificate)) + } + for _, factory := range resourceFactories { res, err := factory(params) if err != nil { diff --git a/internal/naming/main.go b/internal/naming/main.go index def5adbf2a..4c51835d70 100644 --- a/internal/naming/main.go +++ b/internal/naming/main.go @@ -179,3 +179,28 @@ func TargetAllocatorServiceMonitor(otelcol string) string { func OpAMPBridgeServiceAccount(opampBridge string) string { return DNSName(Truncate("%s-opamp-bridge", 63, opampBridge)) } + +// SelfSignedIssuer returns the SelfSigned Issuer name based on the instance. +func SelfSignedIssuer(otelcol string) string { + return DNSName(Truncate("%s-self-signed-issuer", 63, otelcol)) +} + +// CAIssuer returns the CA Issuer name based on the instance. +func CAIssuer(otelcol string) string { + return DNSName(Truncate("%s-ca-issuer", 63, otelcol)) +} + +// CACertificateSecret returns the Secret name based on the instance. +func CACertificate(otelcol string) string { + return DNSName(Truncate("%s-ca-cert", 63, otelcol)) +} + +// TAServerCertificate returns the Certificate name based on the instance. +func TAServerCertificate(otelcol string) string { + return DNSName(Truncate("%s-ta-server-cert", 63, otelcol)) +} + +// TAClientCertificate returns the Certificate name based on the instance. +func TAClientCertificate(otelcol string) string { + return DNSName(Truncate("%s-ta-client-cert", 63, otelcol)) +} diff --git a/main.go b/main.go index fb856fd123..2528cee5d0 100644 --- a/main.go +++ b/main.go @@ -46,10 +46,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" otelv1alpha1 "github.com/open-telemetry/opentelemetry-operator/apis/v1alpha1" otelv1beta1 "github.com/open-telemetry/opentelemetry-operator/apis/v1beta1" "github.com/open-telemetry/opentelemetry-operator/controllers" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect" + "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/certmanager" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/openshift" "github.com/open-telemetry/opentelemetry-operator/internal/autodetect/prometheus" "github.com/open-telemetry/opentelemetry-operator/internal/config" @@ -132,6 +134,7 @@ func main() { annotationsFilter []string webhookPort int tlsOpt tlsConfig + enableTargetAllocatorMTLS bool ) pflag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") @@ -163,6 +166,7 @@ func main() { pflag.StringArrayVar(&annotationsFilter, "annotations-filter", []string{}, "Annotations to filter away from propagating onto deploys. It should be a string array containing patterns, which are literal strings optionally containing a * wildcard character. Example: --annotations-filter=.*filter.out will filter out annotations that looks like: annotation.filter.out: true") pflag.StringVar(&tlsOpt.minVersion, "tls-min-version", "VersionTLS12", "Minimum TLS version supported. Value must match version names from https://golang.org/pkg/crypto/tls/#pkg-constants.") pflag.StringSliceVar(&tlsOpt.cipherSuites, "tls-cipher-suites", nil, "Comma-separated list of cipher suites for the server. Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). If omitted, the default Go cipher suites will be used") + pflag.BoolVar(&enableTargetAllocatorMTLS, constants.FlagTargetAllocatorMTLS, false, "Enable mTLS connection between the target allocator and the controller") pflag.Parse() logger := zap.New(zap.UseFlagOptions(&opts)) @@ -195,6 +199,7 @@ func main() { "enable-nginx-instrumentation", enableNginxInstrumentation, "enable-nodejs-instrumentation", enableNodeJSInstrumentation, "enable-java-instrumentation", enableJavaInstrumentation, + "enable-target-allocator-mtls", enableTargetAllocatorMTLS, ) restConfig := ctrl.GetConfigOrDie() @@ -304,6 +309,12 @@ func main() { } else { setupLog.Info("Openshift CRDs are not installed, skipping adding to scheme.") } + if cfg.CertManagerAvailability() == certmanager.Available { + setupLog.Info("Cert-Manager is installed, adding to scheme.") + utilruntime.Must(cmv1.AddToScheme(scheme)) + } else { + setupLog.Info("Cert-Manager is not installed, skipping adding to scheme.") + } if cfg.AnnotationsFilter() != nil { for _, basePattern := range cfg.AnnotationsFilter() { diff --git a/pkg/constants/env.go b/pkg/constants/env.go index 8ebd1bb5d9..d93505eda1 100644 --- a/pkg/constants/env.go +++ b/pkg/constants/env.go @@ -44,4 +44,6 @@ const ( FlagNginx = "enable-nginx-instrumentation" FlagNodeJS = "enable-nodejs-instrumentation" FlagJava = "enable-java-instrumentation" + + FlagTargetAllocatorMTLS = "enable-target-allocator-mtls" )