Skip to content

Commit

Permalink
feat: allow argocd cluster rotate-auth to accept cluster name (#9838)
Browse files Browse the repository at this point in the history
* feat: allow argocd cluster rotate-auth to accept cluster name

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>

* fix: test assertions

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>

* chore: address pr feedback

Signed-off-by: Daniel Helfand <helfand.4@gmail.com>
  • Loading branch information
danielhelfand committed Jul 8, 2022
1 parent 61c09dd commit 335ffa8
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 58 deletions.
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

0 comments on commit 335ffa8

Please sign in to comment.