-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add capi-auth-token file to control plane machines (#115)
- Loading branch information
1 parent
eab4b8b
commit f60ac2e
Showing
9 changed files
with
241 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package token | ||
|
||
import ( | ||
"context" | ||
cryptorand "crypto/rand" | ||
"encoding/base64" | ||
"fmt" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
const ( | ||
AuthTokenNameSuffix = "capi-auth-token" | ||
) | ||
|
||
// Reconcile ensures that a token secret exists for the given cluster. | ||
func Reconcile(ctx context.Context, c client.Client, clusterKey client.ObjectKey) error { | ||
if _, err := getSecret(ctx, c, clusterKey); err != nil { | ||
if apierrors.IsNotFound(err) { | ||
if _, err := generateAndStore(ctx, c, clusterKey); err != nil { | ||
return fmt.Errorf("failed to generate and store token: %w", err) | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Lookup retrieves the token for the given cluster. | ||
func Lookup(ctx context.Context, c client.Client, clusterKey client.ObjectKey) (string, error) { | ||
secret, err := getSecret(ctx, c, clusterKey) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to get secret: %w", err) | ||
} | ||
|
||
v, ok := secret.Data["token"] | ||
if !ok { | ||
return "", fmt.Errorf("token not found in secret") | ||
} | ||
|
||
return string(v), nil | ||
} | ||
|
||
// authTokenName returns the name of the auth-token secret, computed by convention using the name of the cluster. | ||
func authTokenName(clusterName string) string { | ||
return fmt.Sprintf("%s-%s", clusterName, AuthTokenNameSuffix) | ||
} | ||
|
||
// getSecret retrieves the token secret for the given cluster. | ||
func getSecret(ctx context.Context, c client.Client, clusterKey client.ObjectKey) (*corev1.Secret, error) { | ||
s := &corev1.Secret{} | ||
key := client.ObjectKey{ | ||
Name: authTokenName(clusterKey.Name), | ||
Namespace: clusterKey.Namespace, | ||
} | ||
if err := c.Get(ctx, key, s); err != nil { | ||
return nil, fmt.Errorf("failed to get secret: %w", err) | ||
} | ||
|
||
return s, nil | ||
} | ||
|
||
// generateAndStore generates a new token and stores it in a secret. | ||
func generateAndStore(ctx context.Context, c client.Client, clusterKey client.ObjectKey) (*corev1.Secret, error) { | ||
token, err := randomB64(16) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to generate token: %w", err) | ||
} | ||
|
||
secret := &corev1.Secret{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Namespace: clusterKey.Namespace, | ||
Name: authTokenName(clusterKey.Name), | ||
}, | ||
Data: map[string][]byte{ | ||
"token": []byte(token), | ||
}, | ||
Type: clusterv1.ClusterSecretType, | ||
} | ||
|
||
if err := c.Create(ctx, secret); err != nil { | ||
return nil, fmt.Errorf("failed to create secret: %w", err) | ||
} | ||
|
||
return secret, nil | ||
} | ||
|
||
// randomB64 generates a random base64 string of n bytes. | ||
func randomB64(n int) (string, error) { | ||
b := make([]byte, n) | ||
_, err := cryptorand.Read(b) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to read random bytes: %w", err) | ||
} | ||
return base64.StdEncoding.EncodeToString(b), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package token_test | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"testing" | ||
|
||
. "github.com/onsi/gomega" | ||
corev1 "k8s.io/api/core/v1" | ||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/client/fake" | ||
|
||
"github.com/canonical/cluster-api-bootstrap-provider-microk8s/pkg/token" | ||
) | ||
|
||
func TestReconcile(t *testing.T) { | ||
t.Run("SecretAvailableSucceeds", func(t *testing.T) { | ||
namespace := "test-namespace" | ||
clusterName := "test-cluster" | ||
secret := &corev1.Secret{ | ||
ObjectMeta: v1.ObjectMeta{ | ||
Name: fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix), | ||
Namespace: namespace, | ||
}, | ||
} | ||
c := fake.NewClientBuilder().WithObjects(secret).Build() | ||
|
||
g := NewWithT(t) | ||
|
||
g.Expect(token.Reconcile(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace})).To(Succeed()) | ||
}) | ||
|
||
t.Run("SecretNotFoundGenerates", func(t *testing.T) { | ||
namespace := "test-namespace" | ||
clusterName := "test-cluster" | ||
c := fake.NewClientBuilder().Build() | ||
|
||
g := NewWithT(t) | ||
|
||
g.Expect(token.Reconcile(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace})).To(Succeed()) | ||
|
||
s := &corev1.Secret{} | ||
key := client.ObjectKey{ | ||
Name: fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix), | ||
Namespace: namespace, | ||
} | ||
g.Expect(c.Get(context.Background(), key, s)).To(Succeed()) | ||
g.Expect(s.ObjectMeta.Name).To(Equal(fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix))) | ||
g.Expect(s.ObjectMeta.Namespace).To(Equal(namespace)) | ||
g.Expect(string(s.Data["token"])).ToNot(BeEmpty()) | ||
}) | ||
|
||
t.Run("LookupFailsIfNoSecret", func(t *testing.T) { | ||
namespace := "test-namespace" | ||
clusterName := "test-cluster" | ||
c := fake.NewClientBuilder().Build() | ||
|
||
g := NewWithT(t) | ||
|
||
_, err := token.Lookup(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace}) | ||
g.Expect(err).To(HaveOccurred()) | ||
}) | ||
|
||
t.Run("LookupSucceedsIfSecretExists", func(t *testing.T) { | ||
namespace := "test-namespace" | ||
clusterName := "test-cluster" | ||
expToken := "test-token" | ||
secret := &corev1.Secret{ | ||
ObjectMeta: v1.ObjectMeta{ | ||
Name: fmt.Sprintf("%s-%s", clusterName, token.AuthTokenNameSuffix), | ||
Namespace: namespace, | ||
}, | ||
Data: map[string][]byte{ | ||
"token": []byte(expToken), | ||
}, | ||
} | ||
c := fake.NewClientBuilder().WithObjects(secret).Build() | ||
|
||
g := NewWithT(t) | ||
|
||
token, err := token.Lookup(context.Background(), c, client.ObjectKey{Name: clusterName, Namespace: namespace}) | ||
g.Expect(err).ToNot(HaveOccurred()) | ||
g.Expect(token).To(Equal(expToken)) | ||
}) | ||
} |