Support rotation of svc account tokens
This patchset introduces airshipctl command - airshipctl cluster rotate-sa-token which basically rotates SA tokens Previous work: https://review.opendev.org/#/c/749470/ Change-Id: Ibe932fa8d2831979e5b2ac2781f746e8ec2fdc3c
This commit is contained in:
parent
76e4d3f48c
commit
b7dd46c4e6
@ -17,6 +17,7 @@ package cluster
|
|||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/cmd/cluster/resetsatoken"
|
||||||
"opendev.org/airship/airshipctl/pkg/config"
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ func NewClusterCommand(cfgFactory config.Factory) *cobra.Command {
|
|||||||
clusterRootCmd.AddCommand(NewInitCommand(cfgFactory))
|
clusterRootCmd.AddCommand(NewInitCommand(cfgFactory))
|
||||||
clusterRootCmd.AddCommand(NewMoveCommand(cfgFactory))
|
clusterRootCmd.AddCommand(NewMoveCommand(cfgFactory))
|
||||||
clusterRootCmd.AddCommand(NewStatusCommand(cfgFactory))
|
clusterRootCmd.AddCommand(NewStatusCommand(cfgFactory))
|
||||||
|
clusterRootCmd.AddCommand(resetsatoken.NewResetCommand(cfgFactory))
|
||||||
|
|
||||||
return clusterRootCmd
|
return clusterRootCmd
|
||||||
}
|
}
|
||||||
|
76
cmd/cluster/resetsatoken/resetsatoken.go
Normal file
76
cmd/cluster/resetsatoken/resetsatoken.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 resetsatoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/cluster/resetsatoken"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
resetLong = `
|
||||||
|
Use to reset/rotate the Service Account(SA) tokens and additionally restart the
|
||||||
|
corresponding pods to get the latest token data reflected in the pod spec
|
||||||
|
|
||||||
|
Secret-namespace is a mandatory field and secret-name is optional. If secret-
|
||||||
|
name is not given, all the SA tokens in that particular namespace is considered,
|
||||||
|
else only that particular input secret-name`
|
||||||
|
|
||||||
|
resetExample = `
|
||||||
|
# To rotate a particular SA token
|
||||||
|
airshipctl cluster rotate-sa-token -n cert-manager -s cert-manager-token-vvn9p
|
||||||
|
|
||||||
|
# To rotate all the SA tokens in cert-manager namespace
|
||||||
|
airshipctl cluster rotate-sa-token -n cert-manager
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewResetCommand creates a new command for generating secret information
|
||||||
|
func NewResetCommand(cfgFactory config.Factory) *cobra.Command {
|
||||||
|
r := &resetsatoken.ResetCommand{
|
||||||
|
Options: resetsatoken.ResetFlags{},
|
||||||
|
CfgFactory: cfgFactory,
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCmd := &cobra.Command{
|
||||||
|
Use: "rotate-sa-token",
|
||||||
|
Short: "Rotate tokens of Service Accounts",
|
||||||
|
Long: resetLong[1:],
|
||||||
|
Example: resetExample,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return r.RunE()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resetCmd.Flags().StringVarP(&r.Options.Namespace, "secret-namespace", "n", "",
|
||||||
|
"namespace of the Service Account Token")
|
||||||
|
resetCmd.Flags().StringVarP(&r.Options.SecretName, "secret-name", "s", "",
|
||||||
|
"name of the secret containing Service Account Token")
|
||||||
|
resetCmd.Flags().StringVar(&r.Options.Kubeconfig, "kubeconfig", "",
|
||||||
|
"Path to kubeconfig associated with cluster being managed")
|
||||||
|
|
||||||
|
err := resetCmd.MarkFlagRequired("secret-namespace")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
err = resetCmd.MarkFlagRequired("kubeconfig")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("marking kubeconfig flag required failed: %v", err)
|
||||||
|
}
|
||||||
|
return resetCmd
|
||||||
|
}
|
36
cmd/cluster/resetsatoken/resetsatoken_test.go
Normal file
36
cmd/cluster/resetsatoken/resetsatoken_test.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 resetsatoken_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/cmd/cluster/resetsatoken"
|
||||||
|
"opendev.org/airship/airshipctl/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResetToken(t *testing.T) {
|
||||||
|
cmdTests := []*testutil.CmdTest{
|
||||||
|
{
|
||||||
|
Name: "reset-with-help",
|
||||||
|
CmdLine: "--help",
|
||||||
|
Cmd: resetsatoken.NewResetCommand(nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cmdTests {
|
||||||
|
testutil.RunTest(t, tt)
|
||||||
|
}
|
||||||
|
}
|
24
cmd/cluster/resetsatoken/testdata/TestResetTokenGoldenOutput/reset-with-help.golden
vendored
Normal file
24
cmd/cluster/resetsatoken/testdata/TestResetTokenGoldenOutput/reset-with-help.golden
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Use to reset/rotate the Service Account(SA) tokens and additionally restart the
|
||||||
|
corresponding pods to get the latest token data reflected in the pod spec
|
||||||
|
|
||||||
|
Secret-namespace is a mandatory field and secret-name is optional. If secret-
|
||||||
|
name is not given, all the SA tokens in that particular namespace is considered,
|
||||||
|
else only that particular input secret-name
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
rotate-sa-token [flags]
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
# To rotate a particular SA token
|
||||||
|
airshipctl cluster rotate-sa-token -n cert-manager -s cert-manager-token-vvn9p
|
||||||
|
|
||||||
|
# To rotate all the SA tokens in cert-manager namespace
|
||||||
|
airshipctl cluster rotate-sa-token -n cert-manager
|
||||||
|
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for rotate-sa-token
|
||||||
|
--kubeconfig string Path to kubeconfig associated with cluster being managed
|
||||||
|
-s, --secret-name string name of the secret containing Service Account Token
|
||||||
|
-n, --secret-namespace string namespace of the Service Account Token
|
@ -5,10 +5,11 @@ Usage:
|
|||||||
cluster [command]
|
cluster [command]
|
||||||
|
|
||||||
Available Commands:
|
Available Commands:
|
||||||
help Help about any command
|
help Help about any command
|
||||||
init Deploy cluster-api provider components
|
init Deploy cluster-api provider components
|
||||||
move Move Cluster API objects, provider specific objects and all dependencies to the target cluster
|
move Move Cluster API objects, provider specific objects and all dependencies to the target cluster
|
||||||
status Retrieve statuses of deployed cluster components
|
rotate-sa-token Rotate tokens of Service Accounts
|
||||||
|
status Retrieve statuses of deployed cluster components
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
-h, --help help for cluster
|
-h, --help help for cluster
|
||||||
|
@ -26,5 +26,6 @@ such as getting status and deploying initial infrastructure.
|
|||||||
* [airshipctl](airshipctl.md) - A unified entrypoint to various airship components
|
* [airshipctl](airshipctl.md) - A unified entrypoint to various airship components
|
||||||
* [airshipctl cluster init](airshipctl_cluster_init.md) - Deploy cluster-api provider components
|
* [airshipctl cluster init](airshipctl_cluster_init.md) - Deploy cluster-api provider components
|
||||||
* [airshipctl cluster move](airshipctl_cluster_move.md) - Move Cluster API objects, provider specific objects and all dependencies to the target cluster
|
* [airshipctl cluster move](airshipctl_cluster_move.md) - Move Cluster API objects, provider specific objects and all dependencies to the target cluster
|
||||||
|
* [airshipctl cluster rotate-sa-token](airshipctl_cluster_rotate-sa-token.md) - Rotate tokens of Service Accounts
|
||||||
* [airshipctl cluster status](airshipctl_cluster_status.md) - Retrieve statuses of deployed cluster components
|
* [airshipctl cluster status](airshipctl_cluster_status.md) - Retrieve statuses of deployed cluster components
|
||||||
|
|
||||||
|
49
docs/source/cli/airshipctl_cluster_rotate-sa-token.md
Normal file
49
docs/source/cli/airshipctl_cluster_rotate-sa-token.md
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
## airshipctl cluster rotate-sa-token
|
||||||
|
|
||||||
|
Rotate tokens of Service Accounts
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Use to reset/rotate the Service Account(SA) tokens and additionally restart the
|
||||||
|
corresponding pods to get the latest token data reflected in the pod spec
|
||||||
|
|
||||||
|
Secret-namespace is a mandatory field and secret-name is optional. If secret-
|
||||||
|
name is not given, all the SA tokens in that particular namespace is considered,
|
||||||
|
else only that particular input secret-name
|
||||||
|
|
||||||
|
```
|
||||||
|
airshipctl cluster rotate-sa-token [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# To rotate a particular SA token
|
||||||
|
airshipctl cluster rotate-sa-token -n cert-manager -s cert-manager-token-vvn9p
|
||||||
|
|
||||||
|
# To rotate all the SA tokens in cert-manager namespace
|
||||||
|
airshipctl cluster rotate-sa-token -n cert-manager
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for rotate-sa-token
|
||||||
|
--kubeconfig string Path to kubeconfig associated with cluster being managed
|
||||||
|
-s, --secret-name string name of the secret containing Service Account Token
|
||||||
|
-n, --secret-namespace string namespace of the Service Account Token
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--airshipconf string Path to file for airshipctl configuration. (default "$HOME/.airship/config")
|
||||||
|
--debug enable verbose output
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [airshipctl cluster](airshipctl_cluster.md) - Manage Kubernetes clusters
|
||||||
|
|
60
pkg/cluster/resetsatoken/command.go
Normal file
60
pkg/cluster/resetsatoken/command.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
https://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 resetsatoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResetFlags flags for reset command
|
||||||
|
type ResetFlags struct {
|
||||||
|
Namespace string
|
||||||
|
SecretName string
|
||||||
|
Kubeconfig string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetCommand for reset command
|
||||||
|
type ResetCommand struct {
|
||||||
|
Options ResetFlags
|
||||||
|
CfgFactory config.Factory
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunE implements the functionality for resetsatoken
|
||||||
|
func (c *ResetCommand) RunE() error {
|
||||||
|
airshipconfig, err := c.CfgFactory()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
factory := client.DefaultClient
|
||||||
|
|
||||||
|
kclient, err := factory(airshipconfig.LoadedConfigPath(), c.Options.Kubeconfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manager, err := NewTokenManager(kclient.ClientSet())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Starting Token Rotation")
|
||||||
|
|
||||||
|
err = manager.RotateToken(c.Options.Namespace, c.Options.SecretName)
|
||||||
|
if err != nil {
|
||||||
|
return ErrRotateTokenFail{Err: err.Error()}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
85
pkg/cluster/resetsatoken/command_test.go
Normal file
85
pkg/cluster/resetsatoken/command_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
https://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 resetsatoken_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/cluster/resetsatoken"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/config"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/k8s/client"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/k8s/client/fake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunE(t *testing.T) {
|
||||||
|
airshipConfigPath := "testdata/airshipconfig.yaml"
|
||||||
|
kubeConfigPath := "testdata/kubeconfig.yaml"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
testCaseName string
|
||||||
|
testErr string
|
||||||
|
resetFlags resetsatoken.ResetFlags
|
||||||
|
cfgFactory config.Factory
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testCaseName: "invalid config factory",
|
||||||
|
cfgFactory: func() (*config.Config, error) {
|
||||||
|
return nil, fmt.Errorf("test config error")
|
||||||
|
},
|
||||||
|
resetFlags: resetsatoken.ResetFlags{},
|
||||||
|
testErr: "test config error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testCaseName: "valid config factory",
|
||||||
|
cfgFactory: config.CreateFactory(&airshipConfigPath),
|
||||||
|
resetFlags: resetsatoken.ResetFlags{
|
||||||
|
SecretName: "test-secret",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
},
|
||||||
|
testErr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.testCaseName, func(t *testing.T) {
|
||||||
|
command := resetsatoken.ResetCommand{
|
||||||
|
Options: tt.resetFlags,
|
||||||
|
CfgFactory: tt.cfgFactory,
|
||||||
|
}
|
||||||
|
err := command.RunE()
|
||||||
|
if tt.testErr != "" {
|
||||||
|
assert.Contains(t, err.Error(), tt.testErr)
|
||||||
|
} else {
|
||||||
|
fakeConfig, err := command.CfgFactory()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
factory := client.DefaultClient
|
||||||
|
_, err = factory(fakeConfig.LoadedConfigPath(), kubeConfigPath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
fakeClient := fake.NewClient()
|
||||||
|
assert.NotEmpty(t, fakeClient)
|
||||||
|
|
||||||
|
clientset := fakeClient.ClientSet()
|
||||||
|
fakeManager, err := resetsatoken.NewTokenManager(clientset)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = fakeManager.RotateToken(command.Options.Namespace, command.Options.SecretName)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
46
pkg/cluster/resetsatoken/errors.go
Normal file
46
pkg/cluster/resetsatoken/errors.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 resetsatoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNoSATokenFound is returned if there are no SA tokens found in the provided namespace
|
||||||
|
type ErrNoSATokenFound struct {
|
||||||
|
namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNotSAToken is returned if the user input is not an SA token
|
||||||
|
type ErrNotSAToken struct {
|
||||||
|
secretName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRotateTokenFail is called when there is a failure in rotating the SA token
|
||||||
|
type ErrRotateTokenFail struct {
|
||||||
|
Err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrNoSATokenFound) Error() string {
|
||||||
|
return fmt.Sprintf("no service account tokens found in namespace %s", e.namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrNotSAToken) Error() string {
|
||||||
|
return fmt.Sprintf("%s is not a Service Account Token", e.secretName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrRotateTokenFail) Error() string {
|
||||||
|
return fmt.Sprintf("failed to rotate token: %s", e.Err)
|
||||||
|
}
|
139
pkg/cluster/resetsatoken/resetsatoken.go
Normal file
139
pkg/cluster/resetsatoken/resetsatoken.go
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 resetsatoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
replicaSetKind = "ReplicaSet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenManager manages service account rotation
|
||||||
|
type TokenManager struct {
|
||||||
|
kclient kubernetes.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenManager returns an instance of a TokenManager
|
||||||
|
func NewTokenManager(kclient kubernetes.Interface) (*TokenManager, error) {
|
||||||
|
return &TokenManager{
|
||||||
|
kclient: kclient,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotateToken - rotates token > 1. Deletes the secret and 2. Deletes its pod
|
||||||
|
// Deleting the SA Secret recreates a new secret with a new token information
|
||||||
|
// However, the pods referencing to the old secret needs to be refreshed
|
||||||
|
// manually and hence deleting the pod to allow it to get recreated with new
|
||||||
|
// secret reference
|
||||||
|
func (manager TokenManager) RotateToken(ns string, secretName string) error {
|
||||||
|
if secretName == "" {
|
||||||
|
return manager.rotateAllTokens(ns)
|
||||||
|
}
|
||||||
|
return manager.rotateSingleToken(ns, secretName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteSecret- deletes the secret
|
||||||
|
func (manager TokenManager) deleteSecret(secretName string, ns string) error {
|
||||||
|
return manager.kclient.CoreV1().Secrets(ns).Delete(secretName, &metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// deletePod - identifies the secret relationship with pods and deletes corresponding pods
|
||||||
|
// if its part of replicaset
|
||||||
|
func (manager TokenManager) deletePod(secretName string, ns string) error {
|
||||||
|
pods, err := manager.kclient.CoreV1().Pods(ns).List(metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pod := range pods.Items {
|
||||||
|
for _, volume := range pod.Spec.Volumes {
|
||||||
|
if volume.Name == secretName {
|
||||||
|
if manager.isReplicaSet(pod.OwnerReferences) {
|
||||||
|
log.Printf("Deleting pod - %s in %s", pod.Name, ns)
|
||||||
|
if deleteErr := manager.kclient.CoreV1().Pods(ns).Delete(pod.Name,
|
||||||
|
&metav1.DeleteOptions{}); deleteErr != nil {
|
||||||
|
log.Printf("Failed to delete pod: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotateAllTokens rotates all the tokens in the given namespace
|
||||||
|
func (manager TokenManager) rotateAllTokens(ns string) error {
|
||||||
|
tokenTypeFieldSelector := fmt.Sprintf("type=%s", corev1.SecretTypeServiceAccountToken)
|
||||||
|
listOptions := metav1.ListOptions{FieldSelector: tokenTypeFieldSelector}
|
||||||
|
|
||||||
|
secrets, err := manager.kclient.CoreV1().Secrets(ns).List(listOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(secrets.Items) == 0 {
|
||||||
|
return ErrNoSATokenFound{namespace: ns}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secret := range secrets.Items {
|
||||||
|
err := manager.rotate(secret.Name, secret.Namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotateSingleToken rotates a given token in the given ns
|
||||||
|
func (manager TokenManager) rotateSingleToken(ns string, secretName string) error {
|
||||||
|
secret, err := manager.kclient.CoreV1().Secrets(ns).Get(secretName, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if secret.Type != corev1.SecretTypeServiceAccountToken {
|
||||||
|
return ErrNotSAToken{secretName: secretName}
|
||||||
|
}
|
||||||
|
return manager.rotate(secretName, ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotate performs delete action for secrets and its pods
|
||||||
|
func (manager TokenManager) rotate(secretName string, secretNamespace string) error {
|
||||||
|
log.Printf("Rotating token - %s in %s", secretName, secretNamespace)
|
||||||
|
err := manager.deleteSecret(secretName, secretNamespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return manager.deletePod(secretName, secretNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isReplicaSet checks if the pod is controlled by a ReplicaSet making it safe to delete
|
||||||
|
func (manager TokenManager) isReplicaSet(ownerReferences []metav1.OwnerReference) bool {
|
||||||
|
for _, ownerRef := range ownerReferences {
|
||||||
|
if ownerRef.Kind == replicaSetKind {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
166
pkg/cluster/resetsatoken/resetsatoken_test.go
Normal file
166
pkg/cluster/resetsatoken/resetsatoken_test.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 resetsatoken_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
kfake "k8s.io/client-go/kubernetes/fake"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
ktesting "k8s.io/client-go/testing"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/cluster/resetsatoken"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/k8s/client/fake"
|
||||||
|
"opendev.org/airship/airshipctl/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
existingSecrets []*v1.Secret
|
||||||
|
existingPods []*v1.Pod
|
||||||
|
secretName string
|
||||||
|
secretNamespace string
|
||||||
|
numPodDeletes int
|
||||||
|
numSecretDeletes int
|
||||||
|
expectErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var testCases = []testCase{
|
||||||
|
{
|
||||||
|
name: "no-pods-secrets",
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid-secret-no-pod",
|
||||||
|
secretName: "valid-secret",
|
||||||
|
secretNamespace: "valid-namespace",
|
||||||
|
existingSecrets: []*v1.Secret{getSecret()},
|
||||||
|
numSecretDeletes: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid-secret-no-pod-empty-filter",
|
||||||
|
secretNamespace: "valid-namespace",
|
||||||
|
existingSecrets: []*v1.Secret{getSecret()},
|
||||||
|
numSecretDeletes: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid-secret-no-pod",
|
||||||
|
secretName: "invalid-secret",
|
||||||
|
existingSecrets: []*v1.Secret{getSecret()},
|
||||||
|
secretNamespace: "valid-namespace",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unmatched-secret-pod",
|
||||||
|
secretName: "invalid-secret",
|
||||||
|
secretNamespace: "valid-namespace",
|
||||||
|
existingPods: []*v1.Pod{getPod()},
|
||||||
|
existingSecrets: []*v1.Secret{getSecret()},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "matched-secret-pod",
|
||||||
|
secretName: "valid-secret",
|
||||||
|
secretNamespace: "valid-namespace",
|
||||||
|
existingPods: []*v1.Pod{getPod()},
|
||||||
|
existingSecrets: []*v1.Secret{getSecret()},
|
||||||
|
numPodDeletes: 1,
|
||||||
|
numSecretDeletes: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetSaToken(t *testing.T) {
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
cfg, _ := testutil.InitConfig(t)
|
||||||
|
|
||||||
|
var objects []runtime.Object
|
||||||
|
for _, pod := range testCase.existingPods {
|
||||||
|
objects = append(objects, pod)
|
||||||
|
}
|
||||||
|
for _, secret := range testCase.existingSecrets {
|
||||||
|
objects = append(objects, secret)
|
||||||
|
}
|
||||||
|
ra := fake.WithTypedObjects(objects...)
|
||||||
|
kclient := fake.NewClient(ra)
|
||||||
|
|
||||||
|
assert.NotEmpty(t, kclient)
|
||||||
|
assert.NotEmpty(t, cfg)
|
||||||
|
|
||||||
|
clientset := kclient.ClientSet()
|
||||||
|
manager, err := resetsatoken.NewTokenManager(clientset)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = manager.RotateToken(testCase.secretNamespace, testCase.secretName)
|
||||||
|
if testCase.expectErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
actions := clientset.(*kfake.Clientset).Actions()
|
||||||
|
|
||||||
|
podDeleteActions := filterActions(actions, "pods", "delete")
|
||||||
|
assert.Len(t, podDeleteActions, testCase.numPodDeletes)
|
||||||
|
|
||||||
|
secretDeleteActions := filterActions(actions, "secrets", "delete")
|
||||||
|
assert.Len(t, secretDeleteActions, testCase.numSecretDeletes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecret() *v1.Secret {
|
||||||
|
object := readObjectFromFile("testdata/secret.yaml")
|
||||||
|
if secret, ok := object.(*v1.Secret); ok {
|
||||||
|
return secret
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPod() *v1.Pod {
|
||||||
|
object := readObjectFromFile("testdata/pod.yaml")
|
||||||
|
if pod, ok := object.(*v1.Pod); ok {
|
||||||
|
return pod
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readObjectFromFile(fileName string) runtime.Object {
|
||||||
|
contents, err := ioutil.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
jsonContents, err := yaml.ToJSON(contents)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
object, err := runtime.Decode(scheme.Codecs.UniversalDeserializer(), jsonContents)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterActions(actions []ktesting.Action, resource string, verb string) []ktesting.Action {
|
||||||
|
var result []ktesting.Action
|
||||||
|
for _, action := range actions {
|
||||||
|
if action.GetVerb() == verb && action.GetResource().Resource == resource {
|
||||||
|
result = append(result, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
23
pkg/cluster/resetsatoken/testdata/airshipconfig.yaml
vendored
Normal file
23
pkg/cluster/resetsatoken/testdata/airshipconfig.yaml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: airshipit.org/v1alpha1
|
||||||
|
contexts:
|
||||||
|
dummy_cluster:
|
||||||
|
contextKubeconf: dummycluster_ephemeral
|
||||||
|
manifest: dummy_manifest
|
||||||
|
currentContext: dummy_cluster
|
||||||
|
kind: Config
|
||||||
|
manifests:
|
||||||
|
dummy_manifest:
|
||||||
|
primaryRepositoryName: primary
|
||||||
|
repositories:
|
||||||
|
primary:
|
||||||
|
auth:
|
||||||
|
sshKey: testdata/test-key.pem
|
||||||
|
type: ssh-key
|
||||||
|
checkout:
|
||||||
|
branch: ""
|
||||||
|
force: false
|
||||||
|
remoteRef: ""
|
||||||
|
tag: v1.0.1
|
||||||
|
url: http://dummy.url.com/primary.git
|
||||||
|
subPath: site
|
||||||
|
targetPath: testdata
|
19
pkg/cluster/resetsatoken/testdata/kubeconfig.yaml
vendored
Normal file
19
pkg/cluster/resetsatoken/testdata/kubeconfig.yaml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5RENDQWJDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRFNU1Ea3lPVEUzTURNd09Wb1hEVEk1TURreU5qRTNNRE13T1Zvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTUZyCkdxM0kyb2dZci81Y01Udy9Na1pORTNWQURzdEdyU240WjU2TDhPUGhMcUhDN2t1dno2dVpES3dCSGtGeTBNK2MKRXIzd2piUGE1aTV5NmkyMGtxSHBVMjdPZTA0dzBXV2s4N0RSZVlWaGNoZVJHRXoraWt3SndIcGRmMjJVemZNKwpkSDBzaUhuMVd6UnovYk4za3hMUzJlMnZ2U1Y3bmNubk1YRUd4OXV0MUY0NThHeWxxdmxXTUlWMzg5Q2didXFDCkcwcFdiMTBLM0RVZWdiT25Xa1FmSm5sTWRRVVZDUVdZZEZaaklrcWtkWi9hVTRobkNEV01oZXNWRnFNaDN3VVAKczhQay9BNWh1ZFFPbnFRNDVIWXZLdjZ5RjJWcDUyWExBRUx3NDJ4aVRKZlh0V1h4eHR6cU4wY1lyL2VxeS9XMQp1YVVGSW5xQjFVM0JFL1oxbmFrQ0F3RUFBYU1qTUNFd0RnWURWUjBQQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCCi93UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFKUUVKQVBLSkFjVDVuK3dsWGJsdU9mS0J3c2gKZTI4R1c5R2QwM0N0NGF3RzhzMXE1ZHNua2tpZmVTUENHVFZ1SXF6UTZDNmJaSk9SMDMvVEl5ejh6NDJnaitDVApjWUZXZkltM2RKTnpRL08xWkdySXZZNWdtcWJtWDlpV0JaU24rRytEOGxubzd2aGMvY0tBRFR5OTMvVU92MThuCkdhMnIrRGJJcHcyTWVBVEl2elpxRS9RWlVSQ25DMmdjUFhTVzFqN2h4R3o1a3ZNcGVDZTdQYVUvdVFvblVHSWsKZ2t6ZzI4NHQvREhUUzc4N1V1SUg5cXBaV09yTFNMOGFBeUxQUHhWSXBteGZmbWRETE9TS2VUemRlTmxoSitUMwowQlBVaHBQTlJBNTNJN0hRQjhVUDR2elNONTkzZ1VFbVlFQ2Jic2RYSzB6ZVR6SDdWWHR2Zmd5WTVWWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||||
|
server: https://127.0.0.1:6443
|
||||||
|
name: dummycluster_ephemeral
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: dummycluster_ephemeral
|
||||||
|
user: kubernetes-admin
|
||||||
|
name: dummy_cluster
|
||||||
|
current-context: dummy_cluster
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: kubernetes-admin
|
||||||
|
user:
|
||||||
|
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM4akNDQWRxZ0F3SUJBZ0lJQXhEdzk2RUY4SXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T1RBNU1qa3hOekF6TURsYUZ3MHlNREE1TWpneE56QXpNVEphTURReApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sa3dGd1lEVlFRREV4QnJkV0psY201bGRHVnpMV0ZrCmJXbHVNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQVE4QU1JSUJDZ0tDQVFFQXV6R0pZdlBaNkRvaTQyMUQKSzhXSmFaQ25OQWQycXo1cC8wNDJvRnpRUGJyQWd6RTJxWVZrek9MOHhBVmVSN1NONXdXb1RXRXlGOEVWN3JyLwo0K0hoSEdpcTVQbXF1SUZ5enpuNi9JWmM4alU5eEVmenZpa2NpckxmVTR2UlhKUXdWd2dBU05sMkFXQUloMmRECmRUcmpCQ2ZpS1dNSHlqMFJiSGFsc0J6T3BnVC9IVHYzR1F6blVRekZLdjJkajVWMU5rUy9ESGp5UlJKK0VMNlEKQlltR3NlZzVQNE5iQzllYnVpcG1NVEFxL0p1bU9vb2QrRmpMMm5acUw2Zkk2ZkJ0RjVPR2xwQ0IxWUo4ZnpDdApHUVFaN0hUSWJkYjJ0cDQzRlZPaHlRYlZjSHFUQTA0UEoxNSswV0F5bVVKVXo4WEE1NDRyL2J2NzRKY0pVUkZoCmFyWmlRd0lEQVFBQm95Y3dKVEFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJS3dZQkJRVUgKQXdJd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dFQkFMMmhIUmVibEl2VHJTMFNmUVg1RG9ueVVhNy84aTg1endVWApSd3dqdzFuS0U0NDJKbWZWRGZ5b0hRYUM4Ti9MQkxyUXM0U0lqU1JYdmFHU1dSQnRnT1RRV21Db1laMXdSbjdwCndDTXZQTERJdHNWWm90SEZpUFl2b1lHWFFUSXA3YlROMmg1OEJaaEZ3d25nWUovT04zeG1rd29IN1IxYmVxWEYKWHF1TTluekhESk41VlZub1lQR09yRHMwWlg1RnNxNGtWVU0wVExNQm9qN1ZIRDhmU0E5RjRYNU4yMldsZnNPMAo4aksrRFJDWTAyaHBrYTZQQ0pQS0lNOEJaMUFSMG9ZakZxT0plcXpPTjBqcnpYWHh4S2pHVFVUb1BldVA5dCtCCjJOMVA1TnI4a2oxM0lrend5Q1NZclFVN09ZM3ltZmJobHkrcXZxaFVFa014MlQ1SkpmQT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
|
||||||
|
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBdXpHSll2UFo2RG9pNDIxREs4V0phWkNuTkFkMnF6NXAvMDQyb0Z6UVBickFnekUyCnFZVmt6T0w4eEFWZVI3U041d1dvVFdFeUY4RVY3cnIvNCtIaEhHaXE1UG1xdUlGeXp6bjYvSVpjOGpVOXhFZnoKdmlrY2lyTGZVNHZSWEpRd1Z3Z0FTTmwyQVdBSWgyZERkVHJqQkNmaUtXTUh5ajBSYkhhbHNCek9wZ1QvSFR2MwpHUXpuVVF6Rkt2MmRqNVYxTmtTL0RIanlSUkorRUw2UUJZbUdzZWc1UDROYkM5ZWJ1aXBtTVRBcS9KdW1Pb29kCitGakwyblpxTDZmSTZmQnRGNU9HbHBDQjFZSjhmekN0R1FRWjdIVEliZGIydHA0M0ZWT2h5UWJWY0hxVEEwNFAKSjE1KzBXQXltVUpVejhYQTU0NHIvYnY3NEpjSlVSRmhhclppUXdJREFRQUJBb0lCQVFDU0pycjlaeVpiQ2dqegpSL3VKMFZEWCt2aVF4c01BTUZyUjJsOE1GV3NBeHk1SFA4Vk4xYmc5djN0YUVGYnI1U3hsa3lVMFJRNjNQU25DCm1uM3ZqZ3dVQWlScllnTEl5MGk0UXF5VFBOU1V4cnpTNHRxTFBjM3EvSDBnM2FrNGZ2cSsrS0JBUUlqQnloamUKbnVFc1JpMjRzT3NESlM2UDE5NGlzUC9yNEpIM1M5bFZGbkVuOGxUR2c0M1kvMFZoMXl0cnkvdDljWjR5ZUNpNwpjMHFEaTZZcXJZaFZhSW9RRW1VQjdsbHRFZkZzb3l4VDR6RTE5U3pVbkRoMmxjYTF1TzhqcmI4d2xHTzBoQ2JyClB1R1l2WFFQa3Q0VlNmalhvdGJ3d2lBNFRCVERCRzU1bHp6MmNKeS9zSS8zSHlYbEMxcTdXUmRuQVhhZ1F0VzkKOE9DZGRkb0JBb0dCQU5NcUNtSW94REtyckhZZFRxT1M1ZFN4cVMxL0NUN3ZYZ0pScXBqd2Y4WHA2WHo0KzIvTAozVXFaVDBEL3dGTkZkc1Z4eFYxMnNYMUdwMHFWZVlKRld5OVlCaHVSWGpTZ0ZEWldSY1Z1Y01sNVpPTmJsbmZGCjVKQ0xnNXFMZ1g5VTNSRnJrR3A0R241UDQxamg4TnhKVlhzZG5xWE9xNTFUK1RRT1UzdkpGQjc1QW9HQkFPTHcKalp1cnZtVkZyTHdaVGgvRDNpWll5SVV0ZUljZ2NKLzlzbTh6L0pPRmRIbFd4dGRHUFVzYVd1MnBTNEhvckFtbgpqTm4vSTluUXd3enZ3MWUzVVFPbUhMRjVBczk4VU5hbk5TQ0xNMW1yaXZHRXJ1VHFnTDM1bU41eFZPdTUxQU5JCm4yNkFtODBJT2JDeEtLa0R0ZXJSaFhHd3g5c1pONVJCbG9VRThZNGJBb0dBQ3ZsdVhMZWRxcng5VkE0bDNoNXUKVDJXRVUxYjgxZ1orcmtRc1I1S0lNWEw4cllBTElUNUpHKzFuendyN3BkaEFXZmFWdVV2SDRhamdYT0h6MUs5aQpFODNSVTNGMG9ldUg0V01PY1RwU0prWm0xZUlXcWRiaEVCb1FGdUlWTXRib1BsV0d4ZUhFRHJoOEtreGp4aThSCmdEcUQyajRwY1IzQ0g5QjJ5a0lqQjVFQ2dZRUExc0xXLys2enE1c1lNSm14K1JXZThhTXJmL3pjQnVTSU1LQWgKY0dNK0wwMG9RSHdDaUU4TVNqcVN1ajV3R214YUFuanhMb3ZwSFlRV1VmUEVaUW95UE1YQ2VhRVBLOU4xbk8xMwp0V2lHRytIZkIxaU5PazFCc0lhNFNDbndOM1FRVTFzeXBaeEgxT3hueS9LYmkvYmEvWEZ5VzNqMGFUK2YvVWxrCmJGV1ZVdWtDZ1lFQTBaMmRTTFlmTjV5eFNtYk5xMWVqZXdWd1BjRzQxR2hQclNUZEJxdHFac1doWGE3aDdLTWEKeHdvamh5SXpnTXNyK2tXODdlajhDQ2h0d21sQ1p5QU92QmdOZytncnJ1cEZLM3FOSkpKeU9YREdHckdpbzZmTQp5aXB3Q2tZVGVxRThpZ1J6UkI5QkdFUGY4eVpjMUtwdmZhUDVhM0lRZmxiV0czbGpUemNNZVZjPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
|
25
pkg/cluster/resetsatoken/testdata/pod.yaml
vendored
Normal file
25
pkg/cluster/resetsatoken/testdata/pod.yaml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: valid-pod
|
||||||
|
namespace: valid-namespace
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: apps/v1
|
||||||
|
blockOwnerDeletion: true
|
||||||
|
controller: true
|
||||||
|
kind: ReplicaSet
|
||||||
|
name: valid-pod-rs
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: pod-image
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
|
||||||
|
name: valid-secret
|
||||||
|
readOnly: true
|
||||||
|
serviceAccount: valid-serviceaccount
|
||||||
|
serviceAccountName: valid-serviceaccount
|
||||||
|
volumes:
|
||||||
|
- name: valid-secret
|
||||||
|
secret:
|
||||||
|
defaultMode: 420
|
||||||
|
secretName: valid-secret
|
6
pkg/cluster/resetsatoken/testdata/secret.yaml
vendored
Normal file
6
pkg/cluster/resetsatoken/testdata/secret.yaml
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: valid-secret
|
||||||
|
namespace: valid-namespace
|
||||||
|
type: kubernetes.io/service-account-token
|
Loading…
Reference in New Issue
Block a user