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

feat: allow argocd cluster rotate-auth to accept cluster name #9838

Merged
merged 3 commits into from
Jul 8, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 10 additions & 8 deletions cmd/argocd/commands/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,10 @@ func NewClusterListCommand(clientOpts *argocdclient.ClientOptions) *cobra.Comman
// NewClusterRotateAuthCommand returns a new instance of an `argocd cluster rotate-auth` command
func NewClusterRotateAuthCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "rotate-auth SERVER",
Short: fmt.Sprintf("%s cluster rotate-auth SERVER", cliName),
Example: fmt.Sprintf("%s cluster rotate-auth https://12.34.567.89", cliName),
Use: "rotate-auth SERVER/NAME",
Short: fmt.Sprintf("%s cluster rotate-auth SERVER/NAME", cliName),
Example: `argocd cluster rotate-auth https://12.34.567.89
argocd cluster rotate-auth cluster-name`,
Run: func(c *cobra.Command, args []string) {
ctx := c.Context()

Expand All @@ -384,12 +385,13 @@ func NewClusterRotateAuthCommand(clientOpts *argocdclient.ClientOptions) *cobra.
}
conn, clusterIf := headless.NewClientOrDie(clientOpts, c).NewClusterClientOrDie()
defer io.Close(conn)
clusterQuery := clusterpkg.ClusterQuery{
Server: args[0],
}
_, err := clusterIf.RotateAuth(ctx, &clusterQuery)

cluster := args[0]
clusterQuery := getQueryBySelector(cluster)
_, err := clusterIf.RotateAuth(ctx, clusterQuery)
errors.CheckError(err)
fmt.Printf("Cluster '%s' rotated auth\n", clusterQuery.Server)

fmt.Printf("Cluster '%s' rotated auth\n", cluster)
},
}
return command
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/commands/argocd_cluster.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@ argocd cluster [flags]
* [argocd cluster get](argocd_cluster_get.md) - Get cluster information
* [argocd cluster list](argocd_cluster_list.md) - List configured clusters
* [argocd cluster rm](argocd_cluster_rm.md) - Remove cluster credentials
* [argocd cluster rotate-auth](argocd_cluster_rotate-auth.md) - argocd cluster rotate-auth SERVER
* [argocd cluster rotate-auth](argocd_cluster_rotate-auth.md) - argocd cluster rotate-auth SERVER/NAME

5 changes: 3 additions & 2 deletions docs/user-guide/commands/argocd_cluster_rotate-auth.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
## argocd cluster rotate-auth

argocd cluster rotate-auth SERVER
argocd cluster rotate-auth SERVER/NAME

```
argocd cluster rotate-auth SERVER [flags]
argocd cluster rotate-auth SERVER/NAME [flags]
```

### Examples

```
argocd cluster rotate-auth https://12.34.567.89
argocd cluster rotate-auth cluster-name
```

### Options
Expand Down
114 changes: 67 additions & 47 deletions server/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"context"

"github.com/argoproj/gitops-engine/pkg/utils/kube"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -298,56 +299,75 @@ func (s *Server) RotateAuth(ctx context.Context, q *cluster.ClusterQuery) (*clus
if err != nil {
return nil, err
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(clust.Project, q.Server)); err != nil {
return nil, err
}
logCtx := log.WithField("cluster", q.Server)
logCtx.Info("Rotating auth")
restCfg := clust.RESTConfig()
if restCfg.BearerToken == "" {
return nil, status.Errorf(codes.InvalidArgument, "Cluster '%s' does not use bearer token authentication", q.Server)
}
claims, err := clusterauth.ParseServiceAccountToken(restCfg.BearerToken)
if err != nil {
return nil, err
}
kubeclientset, err := kubernetes.NewForConfig(restCfg)
if err != nil {
return nil, err
}
newSecret, err := clusterauth.GenerateNewClusterManagerSecret(kubeclientset, claims)
if err != nil {
return nil, err
}
// we are using token auth, make sure we don't store client-cert information
clust.Config.KeyData = nil
clust.Config.CertData = nil
clust.Config.BearerToken = string(newSecret.Data["token"])

// Test the token we just created before persisting it
serverVersion, err := s.kubectl.GetServerVersion(clust.RESTConfig())
if err != nil {
return nil, err
}
_, err = s.db.UpdateCluster(ctx, clust)
if err != nil {
return nil, err
}
err = s.cache.SetClusterInfo(clust.Server, &appv1.ClusterInfo{
ServerVersion: serverVersion,
ConnectionState: appv1.ConnectionState{
Status: appv1.ConnectionStatusSuccessful,
ModifiedAt: &v1.Time{Time: time.Now()},
},
})
if err != nil {
return nil, err
var servers []string
if q.Name != "" {
servers, err = s.db.GetClusterServersByName(ctx, q.Name)
if err != nil {
return nil, status.Errorf(codes.NotFound, "failed to get cluster servers by name: %v", err)
}
for _, server := range servers {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(clust.Project, server)); err != nil {
return nil, status.Errorf(codes.PermissionDenied, "encountered permissions issue while processing request: %v", err)
}
}
} else {
if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, createRBACObject(clust.Project, q.Server)); err != nil {
return nil, status.Errorf(codes.PermissionDenied, "encountered permissions issue while processing request: %v", err)
}
servers = append(servers, q.Server)
}
err = clusterauth.RotateServiceAccountSecrets(kubeclientset, claims, newSecret)
if err != nil {
return nil, err

for _, server := range servers {
logCtx := log.WithField("cluster", server)
logCtx.Info("Rotating auth")
restCfg := clust.RESTConfig()
if restCfg.BearerToken == "" {
return nil, status.Errorf(codes.InvalidArgument, "Cluster '%s' does not use bearer token authentication", server)
}

claims, err := clusterauth.ParseServiceAccountToken(restCfg.BearerToken)
if err != nil {
return nil, err
}
kubeclientset, err := kubernetes.NewForConfig(restCfg)
if err != nil {
return nil, err
}
newSecret, err := clusterauth.GenerateNewClusterManagerSecret(kubeclientset, claims)
if err != nil {
return nil, err
}
// we are using token auth, make sure we don't store client-cert information
clust.Config.KeyData = nil
clust.Config.CertData = nil
clust.Config.BearerToken = string(newSecret.Data["token"])

// Test the token we just created before persisting it
serverVersion, err := s.kubectl.GetServerVersion(clust.RESTConfig())
if err != nil {
return nil, err
}
_, err = s.db.UpdateCluster(ctx, clust)
if err != nil {
return nil, err
}
err = s.cache.SetClusterInfo(clust.Server, &appv1.ClusterInfo{
ServerVersion: serverVersion,
ConnectionState: appv1.ConnectionState{
Status: appv1.ConnectionStatusSuccessful,
ModifiedAt: &v1.Time{Time: time.Now()},
},
})
if err != nil {
return nil, err
}
err = clusterauth.RotateServiceAccountSecrets(kubeclientset, claims, newSecret)
if err != nil {
return nil, err
}
logCtx.Infof("Rotated auth (old: %s, new: %s)", claims.SecretName, newSecret.Name)
}
logCtx.Infof("Rotated auth (old: %s, new: %s)", claims.SecretName, newSecret.Name)
return &cluster.ClusterResponse{}, nil
}

Expand Down
91 changes: 91 additions & 0 deletions server/cluster/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cluster

import (
"context"
"encoding/json"
"testing"
"time"

Expand Down Expand Up @@ -205,6 +206,96 @@ func TestDeleteClusterByName(t *testing.T) {
})
}

func TestRotateAuth(t *testing.T) {
testNamespace := "kube-system"
token := "eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhcmdvY2QtbWFuYWdlci10b2tlbi10ajc5ciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhcmdvY2QtbWFuYWdlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjkxZGQzN2NmLThkOTItMTFlOS1hMDkxLWQ2NWYyYWU3ZmE4ZCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphcmdvY2QtbWFuYWdlciJ9.ytZjt2pDV8-A7DBMR06zQ3wt9cuVEfq262TQw7sdra-KRpDpMPnziMhc8bkwvgW-LGhTWUh5iu1y-1QhEx6mtbCt7vQArlBRxfvM5ys6ClFkplzq5c2TtZ7EzGSD0Up7tdxuG9dvR6TGXYdfFcG779yCdZo2H48sz5OSJfdEriduMEY1iL5suZd3ebOoVi1fGflmqFEkZX6SvxkoArl5mtNP6TvZ1eTcn64xh4ws152hxio42E-eSnl_CET4tpB5vgP5BVlSKW2xB7w2GJxqdETA5LJRI_OilY77dTOp8cMr_Ck3EOeda3zHfh4Okflg8rZFEeAuJYahQNeAILLkcA"
config := v1alpha1.ClusterConfig{
BearerToken: token,
}

configMarshal, err := json.Marshal(config)
if err != nil {
t.Errorf("failed to marshal config for test: %v", err)
}

clientset := getClientset(nil, testNamespace,
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-secret",
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
},
Annotations: map[string]string{
common.AnnotationKeyManagedBy: common.AnnotationValueManagedByArgoCD,
},
},
Data: map[string][]byte{
"name": []byte("my-cluster-name"),
"server": []byte("https://my-cluster-name"),
"config": configMarshal,
},
},
&corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "kube-system",
},
},
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-manager-token-tj79r",
Namespace: "kube-system",
},
Data: map[string][]byte{
"token": []byte(token),
},
},
&corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-manager",
Namespace: "kube-system",
},
Secrets: []corev1.ObjectReference{
{
Kind: "Secret",
Name: "argocd-manager-token-tj79r",
},
},
})

db := db.NewDB(testNamespace, settings.NewSettingsManager(context.Background(), clientset, testNamespace), clientset)
server := NewServer(db, newNoopEnforcer(), newServerInMemoryCache(), &kubetest.MockKubectlCmd{})

t.Run("RotateAuth by Unknown Name", func(t *testing.T) {
_, err := server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
Name: "foo",
})

assert.EqualError(t, err, `rpc error: code = PermissionDenied desc = permission denied`)
})

// While the tests results for the next two tests result in an error, they do
// demonstrate the proper mapping of cluster names/server to server info (i.e. my-cluster-name
// results in https://my-cluster-name info being used and https://my-cluster-name results in https://my-cluster-name).
t.Run("RotateAuth by Name - Error from no such host", func(t *testing.T) {
_, err := server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
Name: "my-cluster-name",
})

require.NotNil(t, err)
assert.Contains(t, err.Error(), "Get \"https://my-cluster-name/")
})

t.Run("RotateAuth by Server - Error from no such host", func(t *testing.T) {
_, err := server.RotateAuth(context.Background(), &clusterapi.ClusterQuery{
Server: "https://my-cluster-name",
})

require.NotNil(t, err)
assert.Contains(t, err.Error(), "Get \"https://my-cluster-name/")
})
}

func getClientset(config map[string]string, ns string, objects ...runtime.Object) *fake.Clientset {
secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand Down