Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ web/app/yarn-error.log
vendor
**/*.swp
**/charts/**/charts
go-test.json
package-lock.json
.vscode
**/coverage*
15 changes: 15 additions & 0 deletions cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,21 @@ func installControlPlane(ctx context.Context, k8sAPI *k8s.KubernetesAPI, w io.Wr
}
}

// in order to correctly initialize the issuer credentials the overrides
// (from above) need to be set/applied to the values themselves
// specifically identity issuer scheme, and trust values
//
// marshal+unmarshal here will only apply specific overrides to values and
// will not wipe out values that are not set.
data, err := yaml.Marshal(valuesOverrides)
if err != nil {
return err
}
err = yaml.Unmarshal(data, values)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relies on the "partial unmarshal" behavior where existing fields in values will be retained with their value if the field is not present in data. This is a bit subtle and in a casual reading of this code, one might expect values to be completely replaced by data. I think this could use a comment.

if err != nil {
return err
}

err = initializeIssuerCredentials(ctx, k8sAPI, values)
if err != nil {
return err
Expand Down
246 changes: 246 additions & 0 deletions cli/cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"testing"
Expand All @@ -16,6 +17,7 @@ import (
"github.com/linkerd/linkerd2/pkg/tls"
"helm.sh/helm/v3/pkg/cli/values"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
Expand Down Expand Up @@ -314,6 +316,246 @@ func TestRender(t *testing.T) {
}
}

// TestOverrideIssuer calls install control plane with the goal of testing
// options overrides for initialize issuer credentials.
func TestOverrideIssuer(t *testing.T) {
removeIssuerCrt := func() (*charts.Values, error) {
t.Helper()
values, err := testInstallOptionsFakeCerts()
if err != nil {
return nil, err
}
values.Identity.Issuer.TLS.CrtPEM = ""
return values, nil
}
removeIssuerKey := func() (*charts.Values, error) {
t.Helper()
values, err := testInstallOptionsFakeCerts()
if err != nil {
return nil, err
}
values.Identity.Issuer.TLS.KeyPEM = ""
return values, nil
}
removeTrustAnchor := func() (*charts.Values, error) {
t.Helper()
values, err := testInstallOptionsFakeCerts()
if err != nil {
return nil, err
}
values.IdentityTrustAnchorsPEM = ""
return values, nil
}
read := func(filename string) []byte {
t.Helper()
data, err := os.ReadFile(path.Join("testdata", filename))
if err != nil {
t.Fatalf("cannot read filename=%s err=%v", filename, err)
}
return data
}
// newK8S returns a test implementation of the k8s API; after setting the
// issuer trust anchor and tls crt+key as a secret.
newK8S := func(opts values.Options) *k8s.KubernetesAPI {
t.Helper()
buf := &bytes.Buffer{}
err := renderCRDs(context.Background(), nil, buf, opts, "yaml")
if err != nil {
t.Fatalf("cannot render-crds for new-k8s-api opts=%+v err=%v",
opts, err)
}
api, err := k8s.NewFakeAPIFromManifests([]io.Reader{buf})
Comment on lines +361 to +367
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to account for the CRDs here. Shorter version:

Suggested change
buf := &bytes.Buffer{}
err := renderCRDs(context.Background(), nil, buf, opts, "yaml")
if err != nil {
t.Fatalf("cannot render-crds for new-k8s-api opts=%+v err=%v",
opts, err)
}
api, err := k8s.NewFakeAPIFromManifests([]io.Reader{buf})
api, err := k8s.NewFakeAPI()

if err != nil {
t.Fatalf("cannot create new fake-api from manifests err=%v", err)
}
_, err = api.CoreV1().Secrets(controlPlaneNamespace).Create(context.Background(),
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: k8s.IdentityIssuerSecretName,
Namespace: controlPlaneNamespace,
},
Data: map[string][]byte{
k8s.IdentityIssuerTrustAnchorsNameExternal: read("valid-trust-anchors.pem"),
corev1.TLSCertKey: read("valid-crt.pem"),
corev1.TLSPrivateKeyKey: read("valid-key.pem"),
}}, metav1.CreateOptions{})
if err != nil {
t.Fatalf("cannot create secret for new-k8s-api err=%v", err)
}
return api
}
controlPlaneNamespace = defaultLinkerdNamespace
for i, test := range []struct {
options values.Options
values func() (*charts.Values, error)
k8sAPI *k8s.KubernetesAPI
expErr string
expIdentityTrustAnchor bool
expIssuerCrt bool
expIssuerKey bool
expIssuerName string
}{
{
// no options; no certs in values -> generated anchor; key + crt
options: values.Options{},
values: testInstallValuesNoCertsNoHA,
k8sAPI: nil,
expIdentityTrustAnchor: true,
expIssuerKey: true,
expIssuerCrt: true,
expIssuerName: fmt.Sprintf("identity.%s.%s",
controlPlaneNamespace, "test-override-issuer"),
},
{
// no options; fake certs in values -> fake certs untouched
options: values.Options{},
values: testInstallOptionsFakeCerts,
k8sAPI: nil,
expIdentityTrustAnchor: true,
expIssuerKey: true,
expIssuerCrt: true,
expIssuerName: "identity.linkerd.cluster.local",
},
{
// issuer scheme in options; no certs in values; nil k8s api ->
// error trying to call k8s
options: values.Options{
Values: []string{"identity.issuer.scheme=kubernetes.io/tls"},
},
values: testInstallValuesNoCertsNoHA,
k8sAPI: nil,
expErr: "--ignore-cluster is not supported when --identity-external-issuer=true",
expIdentityTrustAnchor: false,
expIssuerKey: false,
expIssuerCrt: false,
expIssuerName: "",
},
{
// issuer scheme in options; no certs in values; fake k8s api ->
// trust anchor is set
options: values.Options{
Values: []string{"identity.issuer.scheme=kubernetes.io/tls"},
},
values: testInstallValuesNoCertsNoHA,
k8sAPI: newK8S(values.Options{}),
expErr: "",
expIdentityTrustAnchor: true,
expIssuerKey: false,
expIssuerCrt: false,
expIssuerName: "identity.linkerd.cluster.local",
},
{
// no options; fake certs in values; remove trust anchor -> err
options: values.Options{},
values: removeTrustAnchor,
k8sAPI: nil,
expErr: "a trust anchors file must be specified if other credentials are provided",
expIdentityTrustAnchor: false,
expIssuerCrt: true,
expIssuerKey: true,
expIssuerName: "identity.linkerd.cluster.local",
},
{
// no options; fake certs in values; remove issuer crt -> err
options: values.Options{},
values: removeIssuerCrt,
k8sAPI: nil,
expErr: "a certificate file must be specified if other credentials are provided",
expIdentityTrustAnchor: true,
expIssuerCrt: false,
expIssuerName: "identity.linkerd.cluster.local",
expIssuerKey: true,
},
{
// no options; fake certs in values; remove issuer key -> err
options: values.Options{},
values: removeIssuerKey,
k8sAPI: nil,
expErr: "a private key file must be specified if other credentials are provided",
expIdentityTrustAnchor: true,
expIssuerCrt: true,
expIssuerName: "identity.linkerd.cluster.local",
expIssuerKey: false,
},
} {
values, err := test.values()
if err != nil {
t.Fatalf("%02d/test install options failed with an error err=%v", i, err)
}
values.IdentityTrustDomain = "test-override-issuer"
// ensure the install options created above meet expectations (we are
// testing the override not the values)
if values.Identity.Issuer.Scheme != k8s.IdentityIssuerSchemeLinkerd {
t.Fatalf("%02d/identity issuer scheme is incorrect: %s != %s", i,
k8s.IdentityIssuerSchemeLinkerd, values.Identity.Issuer.Scheme)
}
var buf bytes.Buffer
err = installControlPlane(context.Background(), test.k8sAPI, &buf, values, nil, test.options, "yaml")
if test.expErr != "" {
if test.expErr != err.Error() {
t.Fatalf("%02d/install control plane returned incorrect error %s<>%v", i, test.expErr, err)
}
} else {
if err != nil {
t.Fatalf("%02d/install control plane failed with an error=%v", i, err)
}
}
if test.expIdentityTrustAnchor {
if values.IdentityTrustAnchorsPEM == "" {
t.Fatalf("%02d/identity trust-anchors-pem is empty", i)
}
crt, err := tls.DecodePEMCrt(values.IdentityTrustAnchorsPEM)
if err != nil {
t.Fatalf("%02d/generated identity-trust-anchors-pem cannot be decoded", i)
}
if crt == nil {
t.Fatalf("%02d/generated identity-trust-anchors-pem cannot be decoded (nil)", i)
}
if crt.Certificate == nil {
t.Fatalf("%02d/generated identity-trust-anchors-pem certificate is invalid", i)
}
if test.expIssuerName != crt.Certificate.Issuer.CommonName {
t.Fatalf("%02d/generated identity-trust-anchors-pem certificate common-name is incorrect %s<>%s", i,
test.expIssuerName,
crt.Certificate.Issuer.CommonName)
}
} else if values.IdentityTrustAnchorsPEM != "" {
t.Fatalf("%02d/identity was incorrectly set pem=%s", i, values.IdentityTrustAnchorsPEM)
}
if test.expIssuerCrt {
if values.Identity.Issuer.TLS.CrtPEM == "" {
t.Fatalf("%02d/generated identity-issuer-tls-crt-pem is empty", i)
}
crt, err := tls.DecodePEMCrt(values.Identity.Issuer.TLS.CrtPEM)
if err != nil {
t.Fatalf("%02d/generated identity-issuer-tls-crt-pem cannot be decoded err=%v", i, err)
}
if crt == nil {
t.Fatalf("%02d/generated identity-issuer-tls-crt-pem cannot be decoded (nil)", i)
}
if crt.Certificate == nil {
t.Fatalf("%02d/generated identity-issuer-tls-crt-pem certificate is invalid (nil)", i)
}
} else if values.Identity.Issuer.TLS.CrtPEM != "" {
t.Fatalf("%02d/identity issuer crt was incorrectly set pem=%s", i, values.Identity.Issuer.TLS.CrtPEM)
}
if test.expIssuerKey {
if values.Identity.Issuer.TLS.KeyPEM == "" {
t.Fatalf("%02d/generated identity-issuer-tls-key-pem is empty", i)
}
key, err := tls.DecodePEMKey(values.Identity.Issuer.TLS.KeyPEM)
if err != nil {
t.Fatalf("%02d/generated identity-issuer-tls-key-pem cannot be decoded err=%v", i, err)
}
if key == nil {
t.Fatalf("%02d/generated identity-issuer-tls-key-pem cannot be decoded (nil)", i)
}
} else if values.Identity.Issuer.TLS.KeyPEM != "" {
t.Fatalf("%02d/identity issuer tls key was incorrectly set pem=%s", i, values.Identity.Issuer.TLS.KeyPEM)
}
}
}

func TestIgnoreCluster(t *testing.T) {
defaultValues, err := testInstallOptions()
if err != nil {
Expand Down Expand Up @@ -550,6 +792,10 @@ func testInstallOptionsNoCerts(ha bool) (*charts.Values, error) {
return values, nil
}

func testInstallValuesNoCertsNoHA() (*charts.Values, error) {
return testInstallOptionsNoCerts(false)
}

func testInstallValues() (*charts.Values, error) {
values, err := charts.NewValues()
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ go-lint *flags:
golangci-lint run {{ flags }}

go-test:
LINKERD_TEST_PRETTY_DIFF=1 gotestsum -- -race -v -mod=readonly --timeout 10m ./...
LINKERD_TEST_PRETTY_DIFF=1 gotestsum --jsonfile go-test.json -- -race -v -mod=readonly --timeout 10m ./...

##
## Rust
Expand Down
Loading