github.com/argoproj/argo-cd@v1.8.7/server/cluster/cluster.go (about) 1 package cluster 2 3 import ( 4 "time" 5 6 "github.com/argoproj/gitops-engine/pkg/utils/kube" 7 log "github.com/sirupsen/logrus" 8 "golang.org/x/net/context" 9 "google.golang.org/grpc/codes" 10 "google.golang.org/grpc/status" 11 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 "k8s.io/client-go/kubernetes" 13 14 "github.com/argoproj/argo-cd/pkg/apiclient/cluster" 15 appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" 16 servercache "github.com/argoproj/argo-cd/server/cache" 17 "github.com/argoproj/argo-cd/server/rbacpolicy" 18 "github.com/argoproj/argo-cd/util/clusterauth" 19 "github.com/argoproj/argo-cd/util/db" 20 "github.com/argoproj/argo-cd/util/rbac" 21 ) 22 23 // Server provides a Cluster service 24 type Server struct { 25 db db.ArgoDB 26 enf *rbac.Enforcer 27 cache *servercache.Cache 28 kubectl kube.Kubectl 29 } 30 31 // NewServer returns a new instance of the Cluster service 32 func NewServer(db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache, kubectl kube.Kubectl) *Server { 33 return &Server{ 34 db: db, 35 enf: enf, 36 cache: cache, 37 kubectl: kubectl, 38 } 39 } 40 41 // List returns list of clusters 42 func (s *Server) List(ctx context.Context, q *cluster.ClusterQuery) (*appv1.ClusterList, error) { 43 clusterList, err := s.db.ListClusters(ctx) 44 if err != nil { 45 return nil, err 46 } 47 48 items := make([]appv1.Cluster, 0) 49 for _, clust := range clusterList.Items { 50 if s.enf.Enforce(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionGet, clust.Server) { 51 items = append(items, clust) 52 } 53 } 54 err = kube.RunAllAsync(len(items), func(i int) error { 55 items[i] = *s.toAPIResponse(&items[i]) 56 return nil 57 }) 58 if err != nil { 59 return nil, err 60 } 61 clusterList.Items = items 62 return clusterList, nil 63 } 64 65 // Create creates a cluster 66 func (s *Server) Create(ctx context.Context, q *cluster.ClusterCreateRequest) (*appv1.Cluster, error) { 67 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionCreate, q.Cluster.Server); err != nil { 68 return nil, err 69 } 70 c := q.Cluster 71 serverVersion, err := s.kubectl.GetServerVersion(c.RESTConfig()) 72 if err != nil { 73 return nil, err 74 } 75 76 clust, err := s.db.CreateCluster(ctx, c) 77 if status.Convert(err).Code() == codes.AlreadyExists { 78 // act idempotent if existing spec matches new spec 79 existing, getErr := s.db.GetCluster(ctx, c.Server) 80 if getErr != nil { 81 return nil, status.Errorf(codes.Internal, "unable to check existing cluster details: %v", getErr) 82 } 83 84 if existing.Equals(c) { 85 clust = existing 86 } else if q.Upsert { 87 return s.Update(ctx, &cluster.ClusterUpdateRequest{Cluster: c}) 88 } else { 89 return nil, status.Errorf(codes.InvalidArgument, "existing cluster spec is different; use upsert flag to force update") 90 } 91 } 92 err = s.cache.SetClusterInfo(c.Server, &appv1.ClusterInfo{ 93 ServerVersion: serverVersion, 94 ConnectionState: appv1.ConnectionState{ 95 Status: appv1.ConnectionStatusSuccessful, 96 ModifiedAt: &v1.Time{Time: time.Now()}, 97 }, 98 }) 99 if err != nil { 100 return nil, err 101 } 102 return s.toAPIResponse(clust), err 103 } 104 105 // Get returns a cluster from a query 106 func (s *Server) Get(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Cluster, error) { 107 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionGet, q.Server); err != nil { 108 return nil, err 109 } 110 111 c, err := s.getCluster(ctx, q) 112 if err != nil { 113 return nil, err 114 } 115 return s.toAPIResponse(c), nil 116 } 117 118 func (s *Server) getCluster(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Cluster, error) { 119 120 if q.Server != "" { 121 c, err := s.db.GetCluster(ctx, q.Server) 122 if err != nil { 123 return nil, err 124 } 125 return c, nil 126 } 127 128 //we only get the name when we specify Name in ApplicationDestination and next 129 //we want to find the server in order to populate ApplicationDestination.Server 130 if q.Name != "" { 131 clusterList, err := s.db.ListClusters(ctx) 132 if err != nil { 133 return nil, err 134 } 135 for _, c := range clusterList.Items { 136 if c.Name == q.Name { 137 return &c, nil 138 } 139 } 140 } 141 142 return nil, nil 143 } 144 145 var clusterFieldsByPath = map[string]func(updated *appv1.Cluster, existing *appv1.Cluster){ 146 "name": func(updated *appv1.Cluster, existing *appv1.Cluster) { 147 updated.Name = existing.Name 148 }, 149 "namespaces": func(updated *appv1.Cluster, existing *appv1.Cluster) { 150 updated.Namespaces = existing.Namespaces 151 }, 152 "config": func(updated *appv1.Cluster, existing *appv1.Cluster) { 153 updated.Config = existing.Config 154 }, 155 "shard": func(updated *appv1.Cluster, existing *appv1.Cluster) { 156 updated.Shard = existing.Shard 157 }, 158 } 159 160 // Update updates a cluster 161 func (s *Server) Update(ctx context.Context, q *cluster.ClusterUpdateRequest) (*appv1.Cluster, error) { 162 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, q.Cluster.Server); err != nil { 163 return nil, err 164 } 165 166 if len(q.UpdatedFields) != 0 { 167 existing, err := s.db.GetCluster(ctx, q.Cluster.Server) 168 if err != nil { 169 return nil, err 170 } 171 172 for _, path := range q.UpdatedFields { 173 if updater, ok := clusterFieldsByPath[path]; ok { 174 updater(existing, q.Cluster) 175 } 176 } 177 q.Cluster = existing 178 } 179 180 // Test the token we just created before persisting it 181 serverVersion, err := s.kubectl.GetServerVersion(q.Cluster.RESTConfig()) 182 if err != nil { 183 return nil, err 184 } 185 186 clust, err := s.db.UpdateCluster(ctx, q.Cluster) 187 if err != nil { 188 return nil, err 189 } 190 err = s.cache.SetClusterInfo(clust.Server, &appv1.ClusterInfo{ 191 ServerVersion: serverVersion, 192 ConnectionState: appv1.ConnectionState{ 193 Status: appv1.ConnectionStatusSuccessful, 194 ModifiedAt: &v1.Time{Time: time.Now()}, 195 }, 196 }) 197 if err != nil { 198 return nil, err 199 } 200 return s.toAPIResponse(clust), nil 201 } 202 203 // Delete deletes a cluster by name 204 func (s *Server) Delete(ctx context.Context, q *cluster.ClusterQuery) (*cluster.ClusterResponse, error) { 205 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionDelete, q.Server); err != nil { 206 return nil, err 207 } 208 err := s.db.DeleteCluster(ctx, q.Server) 209 return &cluster.ClusterResponse{}, err 210 } 211 212 // RotateAuth rotates the bearer token used for a cluster 213 func (s *Server) RotateAuth(ctx context.Context, q *cluster.ClusterQuery) (*cluster.ClusterResponse, error) { 214 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, q.Server); err != nil { 215 return nil, err 216 } 217 logCtx := log.WithField("cluster", q.Server) 218 logCtx.Info("Rotating auth") 219 clust, err := s.db.GetCluster(ctx, q.Server) 220 if err != nil { 221 return nil, err 222 } 223 restCfg := clust.RESTConfig() 224 if restCfg.BearerToken == "" { 225 return nil, status.Errorf(codes.InvalidArgument, "Cluster '%s' does not use bearer token authentication", q.Server) 226 } 227 claims, err := clusterauth.ParseServiceAccountToken(restCfg.BearerToken) 228 if err != nil { 229 return nil, err 230 } 231 kubeclientset, err := kubernetes.NewForConfig(restCfg) 232 if err != nil { 233 return nil, err 234 } 235 newSecret, err := clusterauth.GenerateNewClusterManagerSecret(kubeclientset, claims) 236 if err != nil { 237 return nil, err 238 } 239 // we are using token auth, make sure we don't store client-cert information 240 clust.Config.KeyData = nil 241 clust.Config.CertData = nil 242 clust.Config.BearerToken = string(newSecret.Data["token"]) 243 244 // Test the token we just created before persisting it 245 serverVersion, err := s.kubectl.GetServerVersion(clust.RESTConfig()) 246 if err != nil { 247 return nil, err 248 } 249 _, err = s.db.UpdateCluster(ctx, clust) 250 if err != nil { 251 return nil, err 252 } 253 err = s.cache.SetClusterInfo(clust.Server, &appv1.ClusterInfo{ 254 ServerVersion: serverVersion, 255 ConnectionState: appv1.ConnectionState{ 256 Status: appv1.ConnectionStatusSuccessful, 257 ModifiedAt: &v1.Time{Time: time.Now()}, 258 }, 259 }) 260 if err != nil { 261 return nil, err 262 } 263 err = clusterauth.RotateServiceAccountSecrets(kubeclientset, claims, newSecret) 264 if err != nil { 265 return nil, err 266 } 267 logCtx.Infof("Rotated auth (old: %s, new: %s)", claims.SecretName, newSecret.Name) 268 return &cluster.ClusterResponse{}, nil 269 } 270 271 func (s *Server) toAPIResponse(clust *appv1.Cluster) *appv1.Cluster { 272 _ = s.cache.GetClusterInfo(clust.Server, &clust.Info) 273 274 clust.Config.Password = "" 275 clust.Config.BearerToken = "" 276 clust.Config.TLSClientConfig.KeyData = nil 277 if clust.Config.ExecProviderConfig != nil { 278 // We can't know what the user has put into args or 279 // env vars on the exec provider that might be sensitive 280 // (e.g. --private-key=XXX, PASSWORD=XXX) 281 // Implicitly assumes the command executable name is non-sensitive 282 clust.Config.ExecProviderConfig.Env = make(map[string]string) 283 clust.Config.ExecProviderConfig.Args = nil 284 } 285 // populate deprecated fields for backward compatibility 286 clust.ServerVersion = clust.Info.ServerVersion 287 clust.ConnectionState = clust.Info.ConnectionState 288 return clust 289 } 290 291 // InvalidateCache invalidates cluster cache 292 func (s *Server) InvalidateCache(ctx context.Context, q *cluster.ClusterQuery) (*appv1.Cluster, error) { 293 if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceClusters, rbacpolicy.ActionUpdate, q.Server); err != nil { 294 return nil, err 295 } 296 cls, err := s.db.GetCluster(ctx, q.Server) 297 if err != nil { 298 return nil, err 299 } 300 now := v1.Now() 301 cls.RefreshRequestedAt = &now 302 cls, err = s.db.UpdateCluster(ctx, cls) 303 if err != nil { 304 return nil, err 305 } 306 return s.toAPIResponse(cls), nil 307 }