Skip to content

Create secrets for serviceaccounts #125

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

Merged
merged 4 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from 3 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
15 changes: 9 additions & 6 deletions pkg/broker/ensure.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/submariner-io/subctl/pkg/gateway"
"github.com/submariner-io/subctl/pkg/namespace"
"github.com/submariner-io/subctl/pkg/role"
"github.com/submariner-io/subctl/pkg/serviceaccount"
"github.com/submariner-io/submariner-operator/pkg/crd"
"github.com/submariner-io/submariner-operator/pkg/lighthouse"
"github.com/submariner-io/submariner-operator/pkg/names"
Expand Down Expand Up @@ -90,7 +91,7 @@ func Ensure(crdUpdater crd.Updater, kubeClient kubernetes.Interface, componentAr

func createBrokerClusterRoleAndDefaultSA(kubeClient kubernetes.Interface, inNamespace string) error {
// Create the a default SA for cluster access (backwards compatibility with documentation)
_, err := CreateNewBrokerSA(kubeClient, submarinerBrokerClusterDefaultSA, inNamespace)
err := CreateNewBrokerSA(kubeClient, submarinerBrokerClusterDefaultSA, inNamespace)
if err != nil && !apierrors.IsAlreadyExists(err) {
return errors.Wrap(err, "error creating the default broker service account")
}
Expand All @@ -114,7 +115,7 @@ func createBrokerClusterRoleAndDefaultSA(kubeClient kubernetes.Interface, inName
func CreateSAForCluster(kubeClient kubernetes.Interface, clusterID, inNamespace string) (*v1.Secret, error) {
saName := names.ForClusterSA(clusterID)

_, err := CreateNewBrokerSA(kubeClient, saName, inNamespace)
err := CreateNewBrokerSA(kubeClient, saName, inNamespace)
if err != nil && !apierrors.IsAlreadyExists(err) {
return nil, errors.Wrap(err, "error creating cluster sa")
}
Expand All @@ -134,7 +135,7 @@ func CreateSAForCluster(kubeClient kubernetes.Interface, clusterID, inNamespace

func createBrokerAdministratorRoleAndSA(kubeClient kubernetes.Interface, inNamespace string) error {
// Create the SA we need for the managing the broker (from subctl, etc..).
_, err := CreateNewBrokerSA(kubeClient, constants.SubmarinerBrokerAdminSA, inNamespace)
err := CreateNewBrokerSA(kubeClient, constants.SubmarinerBrokerAdminSA, inNamespace)
if err != nil && !apierrors.IsAlreadyExists(err) {
return errors.Wrap(err, "error creating the broker admin service account")
}
Expand Down Expand Up @@ -203,7 +204,9 @@ func CreateNewBrokerRoleBinding(kubeClient kubernetes.Interface, serviceAccount,
}

// nolint:wrapcheck // No need to wrap here
func CreateNewBrokerSA(kubeClient kubernetes.Interface, submarinerBrokerSA, inNamespace string) (brokerSA *v1.ServiceAccount, err error) {
return kubeClient.CoreV1().ServiceAccounts(inNamespace).Create(
context.TODO(), NewBrokerSA(submarinerBrokerSA), metav1.CreateOptions{})
func CreateNewBrokerSA(kubeClient kubernetes.Interface, submarinerBrokerSA, inNamespace string) (err error) {
sa := NewBrokerSA(submarinerBrokerSA)
_, err = serviceaccount.Ensure(kubeClient, inNamespace, sa, true)

return err
}
168 changes: 162 additions & 6 deletions pkg/serviceaccount/ensure.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,184 @@ package serviceaccount

import (
"context"
"fmt"
"math/rand"
"strings"
"time"

"github.com/pkg/errors"
"github.com/submariner-io/admiral/pkg/resource"
resourceutil "github.com/submariner-io/subctl/pkg/resource"
"github.com/submariner-io/subctl/pkg/secret"
"github.com/submariner-io/submariner-operator/pkg/embeddedyamls"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
)

// EnsureFromYAML creates the given service account.
const (
createdByAnnotation = "kubernetes.io/created-by"
creatorName = "subctl"
)

// ensureFromYAML creates the given service account.
// nolint:wrapcheck // No need to wrap errors here.
func EnsureFromYAML(kubeClient kubernetes.Interface, namespace, yaml string) (bool, error) {
func ensureFromYAML(kubeClient kubernetes.Interface, namespace, yaml string) (*corev1.ServiceAccount, error) {
sa := &corev1.ServiceAccount{}

err := embeddedyamls.GetObject(yaml, sa)
if err != nil {
return false, err
return nil, err
}

err = ensure(kubeClient, namespace, sa, true)
if err != nil {
return nil, err
}

return sa, err
}

// nolint:wrapcheck // No need to wrap errors here.
func ensure(kubeClient kubernetes.Interface, namespace string, sa *corev1.ServiceAccount, onlyCreate bool) error {
if onlyCreate {
_, err := kubeClient.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), sa.Name, metav1.GetOptions{})

if err == nil || !apierrors.IsNotFound(err) {
return err
}
}

return Ensure(kubeClient, namespace, sa)
_, err := resourceutil.CreateOrUpdate(context.TODO(), resource.ForServiceAccount(kubeClient, namespace), sa)

return err
}

// nolint:wrapcheck // No need to wrap errors here.
func Ensure(kubeClient kubernetes.Interface, namespace string, sa *corev1.ServiceAccount) (bool, error) {
return resourceutil.CreateOrUpdate(context.TODO(), resource.ForServiceAccount(kubeClient, namespace), sa)
func Ensure(kubeClient kubernetes.Interface, namespace string, sa *corev1.ServiceAccount, onlyCreate bool) (*corev1.ServiceAccount, error) {
err := ensure(kubeClient, namespace, sa, onlyCreate)
if err != nil {
return nil, err
}

_, err = EnsureSecretFromSA(kubeClient, sa.Name, namespace)

if err != nil {
return nil, errors.Wrap(err, "failed to get secret for broker SA")
}

return kubeClient.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), sa.Name, metav1.GetOptions{})
}

// EnsureFromYAML creates the given service account and secret for it.
func EnsureFromYAML(kubeClient kubernetes.Interface, namespace, yaml string) (bool, error) {
sa, err := ensureFromYAML(kubeClient, namespace, yaml)
if err != nil {
return false, errors.Wrap(err, "error provisioning the ServiceAccount resource")
}

saSecret, err := EnsureSecretFromSA(kubeClient, sa.Name, namespace)
if err != nil {
return false, errors.Wrap(err, "error creating secret for ServiceAccount resource")
}

return sa != nil && saSecret != nil, nil
}

func EnsureSecretFromSA(client kubernetes.Interface, saName, namespace string) (*corev1.Secret, error) {
sa, err := client.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), saName, metav1.GetOptions{})
if err != nil {
return nil, errors.Wrapf(err, "failed to get ServiceAccount %s/%s", namespace, saName)
}

saSecret := getSecretFromSA(client, sa)

if saSecret != nil {
return saSecret, nil
}

// We couldn't find right secret from this SA, search all Secrets
saSecret, err = getSecretForSA(client, sa)
if err != nil && !apierrors.IsNotFound(err) {
return nil, err
}

if err != nil {
newSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-token-%s", sa.Name, generateRandomString(5)),
Copy link
Member

Choose a reason for hiding this comment

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

Can’t we use GenerateName here?

Copy link
Member

Choose a reason for hiding this comment

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

We can fix this up later, this shouldn’t block the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used GenerateName in intial draft, but then needed to do another Get to find the generated name. Went with this coz similar to what OCP's controller is doing.

Namespace: namespace,
Annotations: map[string]string{
corev1.ServiceAccountNameKey: saName,
createdByAnnotation: creatorName,
},
},
Type: corev1.SecretTypeServiceAccountToken,
}

saSecret, err = secret.Ensure(client, newSecret.Namespace, newSecret)
if err != nil {
return nil, errors.Wrapf(err, "failed to create secret for ServiceAccount %v", saName)
}
}

secretRef := corev1.ObjectReference{
Name: saSecret.Name,
}

sa.Secrets = append(sa.Secrets, secretRef)
err = ensure(client, namespace, sa, false)

if err != nil {
return nil, errors.Wrapf(err, "failed to update ServiceAccount %v with Secret reference %v", saName, secretRef.Name)
}

return saSecret, nil
}

func getSecretFromSA(client kubernetes.Interface, sa *corev1.ServiceAccount) *corev1.Secret {
secretNamePrefix := fmt.Sprintf("%s-token-", sa.Name)
for _, saSecretRef := range sa.Secrets {
if strings.HasPrefix(saSecretRef.Name, secretNamePrefix) {
saSecret, _ := client.CoreV1().Secrets(sa.Namespace).Get(context.TODO(), saSecretRef.Name, metav1.GetOptions{})
if saSecret.Annotations[corev1.ServiceAccountNameKey] == sa.Name && saSecret.Type == corev1.SecretTypeServiceAccountToken {
return saSecret
}
}
}

return nil
}

func getSecretForSA(client kubernetes.Interface, sa *corev1.ServiceAccount) (*corev1.Secret, error) {
saSecrets, err := client.CoreV1().Secrets(sa.Namespace).List(context.TODO(), metav1.ListOptions{
FieldSelector: fields.OneTermEqualSelector("type", "kubernetes.io/service-account-token").String(),
})
if err != nil {
return nil, errors.Wrapf(err, "failed to get secrets of type service-account-token in %v", sa.Namespace)
}

for i := 0; i < len(saSecrets.Items); i++ {
if saSecrets.Items[i].Annotations[corev1.ServiceAccountNameKey] == sa.Name {
return &saSecrets.Items[i], nil
}
}

return nil, apierrors.NewNotFound(schema.GroupResource{
Group: corev1.SchemeGroupVersion.Group,
Resource: "secrets",
}, sa.Name)
}

// nolint:gosec // we need a pseudo random string for name.
func generateRandomString(length int) string {
rand.Seed(time.Now().UnixNano())

s := make([]byte, length)
rand.Read(s)

return fmt.Sprintf("%x", s)[:length]
}
6 changes: 3 additions & 3 deletions pkg/serviceaccount/ensure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ var _ = Describe("EnsureFromYAML", func() {
})

When("the ServiceAccount already exists", func() {
It("should not update it", func() {
It("should not return any error", func() {
_, err := serviceaccount.Ensure(client, namespace, &corev1.ServiceAccount{
TypeMeta: metav1.TypeMeta{
Kind: "ServiceAccount",
Expand All @@ -71,12 +71,12 @@ var _ = Describe("EnsureFromYAML", func() {
ObjectMeta: metav1.ObjectMeta{
Name: "test-sa",
},
})
}, false)
Expect(err).To(Succeed())
assertServiceAccount()

created, err := serviceaccount.EnsureFromYAML(client, namespace, roleYAML)
Expect(created).To(BeFalse())
Expect(created).To(BeTrue())
Expect(err).To(Succeed())
})
})
Expand Down