Skip to content
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

flag to set ServiceAccount token expiry #30

Merged
merged 4 commits into from
Apr 17, 2024
Merged
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
24 changes: 14 additions & 10 deletions pkg/cmd/generate/cli_configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
kubeSawAdminsFile, outDir string
dev bool
kubeconfigs []string
tokenExpirationDays uint
}

func NewCliConfigsCmd() *cobra.Command {
Expand All @@ -51,6 +52,7 @@

configDirPath := fmt.Sprintf("%s/src/github.com/kubesaw/ksctl/out/config", os.Getenv("GOPATH"))
command.Flags().StringVarP(&f.outDir, "out-dir", "o", configDirPath, "Directory where generated ksctl.yaml files should be stored")
command.Flags().UintVarP(&f.tokenExpirationDays, "token-expiration-days", "e", 365, "Expiration time of the ServiceAccount tokens in days")

Check warning on line 55 in pkg/cmd/generate/cli_configs.go

View check run for this annotation

Codecov / codecov/patch

pkg/cmd/generate/cli_configs.go#L55

Added line #L55 was not covered by tests

defaultKubeconfigPath := ""
if home := homedir.HomeDir(); home != "" {
Expand Down Expand Up @@ -88,10 +90,11 @@
}

ctx := &generateContext{
Terminal: term,
newRESTClient: newExternalClient,
kubeSawAdmins: kubeSawAdmins,
kubeconfigPaths: flags.kubeconfigs,
Terminal: term,
newRESTClient: newExternalClient,
kubeSawAdmins: kubeSawAdmins,
kubeconfigPaths: flags.kubeconfigs,
tokenExpirationDays: flags.tokenExpirationDays,
}

// ksctlConfigsPerName contains all ksctlConfig objects that will be marshalled to ksctl.yaml files
Expand Down Expand Up @@ -159,9 +162,10 @@

type generateContext struct {
ioutils.Terminal
newRESTClient NewRESTClientFromConfigFunc
kubeSawAdmins *assets.KubeSawAdmins
kubeconfigPaths []string
newRESTClient NewRESTClientFromConfigFunc
kubeSawAdmins *assets.KubeSawAdmins
kubeconfigPaths []string
tokenExpirationDays uint
}

// contains tokens mapped by SA name
Expand Down Expand Up @@ -196,7 +200,7 @@
ctx.Printlnf("Getting token for SA '%s' in namespace '%s'", sa.Name, saNamespace)
token, err := getServiceAccountToken(externalClient, types.NamespacedName{
Namespace: saNamespace,
Name: sa.Name})
Name: sa.Name}, ctx.tokenExpirationDays)
if token == "" || err != nil {
return err
}
Expand Down Expand Up @@ -243,10 +247,10 @@
// NOTE: due to a changes in OpenShift 4.11, tokens are not listed as `secrets` in ServiceAccounts.
// The recommended solution is to use the TokenRequest API when server version >= 4.11
// (see https://docs.openshift.com/container-platform/4.11/release_notes/ocp-4-11-release-notes.html#ocp-4-11-notable-technical-changes)
func getServiceAccountToken(cl *rest.RESTClient, namespacedName types.NamespacedName) (string, error) {
func getServiceAccountToken(cl *rest.RESTClient, namespacedName types.NamespacedName, tokenExpirationDays uint) (string, error) {
tokenRequest := &authv1.TokenRequest{
Spec: authv1.TokenRequestSpec{
ExpirationSeconds: pointer.Int64(int64(365 * 24 * 60 * 60)), // token will be valid for 1 year
ExpirationSeconds: pointer.Int64(int64(tokenExpirationDays * 24 * 60 * 60)),
},
}
result := &authv1.TokenRequest{}
Expand Down
62 changes: 44 additions & 18 deletions pkg/cmd/generate/cli_configs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@ package generate
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"testing"

"github.com/h2non/gock"
"github.com/kubesaw/ksctl/pkg/client"
"github.com/kubesaw/ksctl/pkg/configuration"
. "github.com/kubesaw/ksctl/pkg/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
authv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"

"github.com/h2non/gock"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

func TestGenerateCliConfigs(t *testing.T) {
Expand All @@ -46,15 +47,15 @@ func TestGenerateCliConfigs(t *testing.T) {
setupGockForListServiceAccounts(t, Member1ServerAPI, configuration.Member)
setupGockForListServiceAccounts(t, Member2ServerAPI, configuration.Member)

setupGockForServiceAccounts(t, HostServerAPI,
setupGockForServiceAccounts(t, HostServerAPI, 50,
newServiceAccount("sandbox-sre-host", "john"),
newServiceAccount("sandbox-sre-host", "bob"),
)
setupGockForServiceAccounts(t, Member1ServerAPI,
setupGockForServiceAccounts(t, Member1ServerAPI, 50,
newServiceAccount("sandbox-sre-member", "john"),
newServiceAccount("sandbox-sre-member", "bob"),
)
setupGockForServiceAccounts(t, Member2ServerAPI,
setupGockForServiceAccounts(t, Member2ServerAPI, 50,
newServiceAccount("sandbox-sre-member", "john"),
newServiceAccount("sandbox-sre-member", "bob"),
)
Expand All @@ -73,7 +74,7 @@ func TestGenerateCliConfigs(t *testing.T) {
// given
tempDir, err := os.MkdirTemp("", "sandbox-sre-out-")
require.NoError(t, err)
flags := generateFlags{kubeconfigs: kubeconfigFiles, kubeSawAdminsFile: configFile, outDir: tempDir}
flags := generateFlags{kubeconfigs: kubeconfigFiles, kubeSawAdminsFile: configFile, outDir: tempDir, tokenExpirationDays: 50}

// when
err = generate(term, flags, newExternalClient)
Expand Down Expand Up @@ -101,7 +102,7 @@ func TestGenerateCliConfigs(t *testing.T) {
configFile := createKubeSawAdminsFile(t, "kubesaw.host.openshiftapps.com", kubeSawAdminsContent)
tempDir, err := os.MkdirTemp("", "sandbox-sre-out-")
require.NoError(t, err)
flags := generateFlags{kubeconfigs: kubeconfigFiles, kubeSawAdminsFile: configFile, outDir: tempDir}
flags := generateFlags{kubeconfigs: kubeconfigFiles, kubeSawAdminsFile: configFile, outDir: tempDir, tokenExpirationDays: 50}

// when
err = generate(term, flags, newExternalClient)
Expand All @@ -115,14 +116,14 @@ func TestGenerateCliConfigs(t *testing.T) {
t.Run("in dev mode", func(t *testing.T) {
// given
setupGockForListServiceAccounts(t, HostServerAPI, configuration.Member)
setupGockForServiceAccounts(t, HostServerAPI,
setupGockForServiceAccounts(t, HostServerAPI, 50,
newServiceAccount("sandbox-sre-member", "john"),
newServiceAccount("sandbox-sre-member", "bob"),
)
tempDir, err := os.MkdirTemp("", "sandbox-sre-out-")
require.NoError(t, err)
kubeconfigFiles := createKubeconfigFiles(t, ksctlKubeconfigContent)
flags := generateFlags{kubeconfigs: kubeconfigFiles, kubeSawAdminsFile: configFile, outDir: tempDir, dev: true}
flags := generateFlags{kubeconfigs: kubeconfigFiles, kubeSawAdminsFile: configFile, outDir: tempDir, dev: true, tokenExpirationDays: 50}

// when
err = generate(term, flags, newExternalClient)
Expand Down Expand Up @@ -157,10 +158,11 @@ func TestGenerateCliConfigs(t *testing.T) {
path := fmt.Sprintf("api/v1/namespaces/%s/serviceaccounts/", sandboxSRENamespace(configuration.Host))
gock.New("https://dummy.openshift.com").Get(path).Persist().Reply(403)
ctx := &generateContext{
Terminal: term,
newRESTClient: newExternalClient,
kubeSawAdmins: kubeSawAdmins,
kubeconfigPaths: kubeconfigFiles,
Terminal: term,
newRESTClient: newExternalClient,
kubeSawAdmins: kubeSawAdmins,
kubeconfigPaths: kubeconfigFiles,
tokenExpirationDays: 365,
}

// when
Expand Down Expand Up @@ -228,7 +230,7 @@ func TestGetServiceAccountToken(t *testing.T) {
// given
require.NoError(t, client.AddToScheme())

setupGockForServiceAccounts(t, "https://api.example.com", newServiceAccount("openshift-customer-monitoring", "loki"))
setupGockForServiceAccounts(t, "https://api.example.com", 365, newServiceAccount("openshift-customer-monitoring", "loki"))
t.Cleanup(gock.OffAll)
cl, err := client.NewRESTClient("secret_token", "https://api.example.com")
cl.Client.Transport = gock.DefaultTransport // make sure that the underlying client's request are intercepted by Gock
Expand All @@ -238,7 +240,7 @@ func TestGetServiceAccountToken(t *testing.T) {
actualToken, err := getServiceAccountToken(cl, types.NamespacedName{
Namespace: "openshift-customer-monitoring",
Name: "loki",
})
}, 365)

// then
require.NoError(t, err)
Expand Down Expand Up @@ -343,7 +345,7 @@ func setupGockForListServiceAccounts(t *testing.T, apiEndpoint string, clusterTy
BodyString(string(resultServiceAccountsStr))
}

func setupGockForServiceAccounts(t *testing.T, apiEndpoint string, sas ...*corev1.ServiceAccount) {
func setupGockForServiceAccounts(t *testing.T, apiEndpoint string, tokenExpirationDays int, sas ...*corev1.ServiceAccount) {
for _, sa := range sas {
expectedToken := "token-secret-for-" + sa.Name
resultTokenRequest := &authv1.TokenRequest{
Expand All @@ -357,6 +359,30 @@ func setupGockForServiceAccounts(t *testing.T, apiEndpoint string, sas ...*corev
t.Logf("mocking access to POST %s/%s", apiEndpoint, path)
gock.New(apiEndpoint).
Post(path).
AddMatcher(func(request *http.Request, _ *gock.Request) (bool, error) {
requestBody, err := io.ReadAll(request.Body)
if err != nil {
return false, err
}
if err := request.Body.Close(); err != nil {
return false, err
}
tokenRequest := &authv1.TokenRequest{}
if err := json.Unmarshal(requestBody, tokenRequest); err != nil {
return false, err
}
fmt.Println(tokenRequest)
expectedExpiry := int64(tokenExpirationDays * 24 * 60 * 60)
if tokenRequest.Spec.ExpirationSeconds == nil {
assert.NotEmpty(t, tokenRequest.Spec.ExpirationSeconds)
return false, nil
}
if *tokenRequest.Spec.ExpirationSeconds != expectedExpiry {
assert.Equal(t, expectedExpiry, *tokenRequest.Spec.ExpirationSeconds)
return false, nil
}
return true, nil
}).
Persist().
Reply(200).
BodyString(string(resultTokenRequestStr))
Expand Down
Loading