1 package resolver
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7
8 "google.golang.org/grpc/codes"
9 "google.golang.org/grpc/status"
10
11 "edge-infra.dev/pkg/edge/api/graph/mapper"
12 "edge-infra.dev/pkg/edge/api/graph/model"
13 "edge-infra.dev/pkg/edge/api/middleware"
14 "edge-infra.dev/pkg/edge/api/types"
15 "edge-infra.dev/pkg/edge/chariot/client"
16 "edge-infra.dev/pkg/sds/clustersecrets"
17 "edge-infra.dev/pkg/sds/clustersecrets/audit"
18 cc "edge-infra.dev/pkg/sds/clustersecrets/common"
19 )
20
21 var (
22
23
24 ErrBannerOptedOutOfCompliance = errors.New("cluster secret lease feature has been turned off as the banner has opted out of edge security compliance")
25
26
27 ErrBannerOptedIntoCompliance = errors.New("updating cluster secrets has been turned off as the banner has opted in to edge security compliance")
28
29 ErrInvalidClusterSecretType = errors.New("invalid cluster secret type")
30
31 ErrLeaseNotObtained = errors.New("cluster secret lease could not be obtained")
32
33 ErrSecretNotRotated = errors.New("cluster secret rotation failed")
34
35 ErrSecretExpired = errors.New("cluster secret expired, please try again shortly")
36 )
37
38
39 func (r *Resolver) GetBannerEdgeSecurityCompliance(ctx context.Context, clusterEdgeID string) (bool, error) {
40 cluster, err := r.StoreClusterService.GetClusterByClusterEdgeID(ctx, clusterEdgeID)
41 if err != nil {
42 return true, err
43 }
44 banner, err := r.BannerService.GetBanner(ctx, cluster.BannerEdgeID)
45 if err != nil {
46 return true, err
47 }
48 return banner.OptInEdgeSecurityCompliance, nil
49 }
50
51
52 func (r *Resolver) updateClusterSecret(ctx context.Context, clusterEdgeID string, secretType model.ClusterSecretType, secretValue string) (bool, error) {
53 cluster, err := r.StoreClusterService.GetCluster(ctx, clusterEdgeID)
54 if err != nil {
55 return false, err
56 }
57
58 isSecCompEnabled, err := r.GetBannerEdgeSecurityCompliance(ctx, clusterEdgeID)
59 if err != nil {
60 return false, err
61 }
62 if isSecCompEnabled {
63 return false, ErrBannerOptedIntoCompliance
64 }
65
66 secretClient, err := r.GCPClientService.GetSecretClient(ctx, cluster.ProjectID)
67 if err != nil {
68 return false, err
69 }
70
71
72 user := middleware.ForContext(ctx)
73 auditLog := audit.New("api")
74 for _, secret := range clustersecrets.List() {
75 if secret.Type() != secretType {
76 continue
77 }
78
79
80 if err := secret.Refresh(secretValue); err != nil {
81 return false, err
82 }
83
84
85 if err := secret.Apply(ctx, secretClient, clusterEdgeID); err != nil {
86 auditLog.Log(secret.Type(), audit.WriteRequest, "username", user.Username, "clusterEdgeID", cluster.ClusterEdgeID, "status", "failed")
87 return false, err
88 }
89
90
91 auditLog.Log(secret.Type(), audit.WriteRequest, "username", user.Username, "clusterEdgeID", cluster.ClusterEdgeID, "version", secret.Version())
92
93 extSecret, _ := secret.ExternalSecret(cluster.ClusterEdgeID, cluster.ProjectID)
94
95 externalSecretBase64, err := mapper.ToBase64StringFromExternalSecret(extSecret)
96 if err != nil {
97 return false, err
98 }
99 err = r.sendChariotMessage(ctx, cluster.ProjectID, cluster.ClusterEdgeID, client.Create, externalSecretBase64)
100 return err == nil, err
101 }
102 return false, fmt.Errorf("%w: %s", ErrInvalidClusterSecretType, secretType)
103 }
104
105
106 func (r *Resolver) fetchClusterSecretPlainValue(ctx context.Context, clusterEdgeID string, secretType model.ClusterSecretType, version string) (string, error) {
107
108 user := middleware.ForContext(ctx)
109 auditLog := audit.New("api")
110
111
112 auditLog.Log(secretType, audit.ReadRequest, "action_by", user.Username, "clusterEdgeID", clusterEdgeID, "version", version)
113
114 secret, err := r.fetchClusterSecret(ctx, clusterEdgeID, secretType, version)
115 if err != nil {
116 return "", err
117 }
118 return secret.Plain(), nil
119 }
120
121
122 func (r *Resolver) fetchClusterSecret(ctx context.Context, clusterEdgeID string, secretType model.ClusterSecretType, version string) (cc.Secret, error) {
123 secretClient, err := r.getSecretClient(ctx, clusterEdgeID)
124 if err != nil {
125 return nil, err
126 }
127
128 isSecCompEnabled, err := r.GetBannerEdgeSecurityCompliance(ctx, clusterEdgeID)
129 if err != nil {
130 return nil, err
131 }
132 if isSecCompEnabled {
133 if err := r.obtainClusterSecretLease(ctx, clusterEdgeID, secretType, secretClient); err != nil {
134 return nil, err
135 }
136 }
137 for _, secret := range clustersecrets.List() {
138 if secret.Type() != secretType {
139 continue
140 }
141 fetchErr := secret.Fetch(ctx, secretClient, clusterEdgeID, version)
142
143 if status.Code(fetchErr) == codes.NotFound {
144 if err := r.handleSecretNotFound(ctx, clusterEdgeID, secret, secretClient); err != nil {
145 return nil, err
146 }
147 } else if status.Code(fetchErr) != codes.OK && !errors.Is(fetchErr, cc.ErrInvalidFormat) {
148 return nil, fetchErr
149 }
150
151
152 if errors.Is(fetchErr, cc.ErrInvalidFormat) {
153 if err := r.reformatSecret(ctx, secretClient, secret, clusterEdgeID, fetchErr); err != nil {
154 return nil, err
155 }
156 }
157 return secret, nil
158 }
159 return nil, fmt.Errorf("%w: %s", ErrInvalidClusterSecretType, secretType)
160 }
161
162 func (r *Resolver) getSecretClient(ctx context.Context, clusterEdgeID string) (types.SecretManagerService, error) {
163 cluster, err := r.StoreClusterService.GetCluster(ctx, clusterEdgeID)
164 if err != nil {
165 return nil, err
166 }
167 secretClient, err := r.GCPClientService.GetSecretClient(ctx, cluster.ProjectID)
168 if err != nil {
169 return nil, err
170 }
171 return secretClient, nil
172 }
173
174 func (r *Resolver) handleSecretNotFound(ctx context.Context, clusterEdgeID string, secret cc.Secret, secretClient types.SecretManagerService) error {
175 auditLog := audit.New("api")
176 if err := secret.Refresh(""); err != nil {
177 return err
178 }
179 if err := secret.Apply(ctx, secretClient, clusterEdgeID); err != nil {
180 auditLog.Log(secret.Type(), audit.WriteRequest, "action_by", "api", "reason", "registration", "clusterEdgeID", clusterEdgeID)
181 return err
182 }
183 auditLog.Log(secret.Type(), audit.WriteRequest, "action_by", "api", "reason", "registration", "clusterEdgeID", clusterEdgeID, "version", secret.Version())
184 return nil
185 }
186
187
188
189 func (r *Resolver) reformatSecret(ctx context.Context, sm types.SecretManagerService, secret cc.Secret, clusterEdgeID string, fetchErr error) error {
190 auditLog := audit.New("api")
191 var err error
192 var usedExistingSecretValue bool
193 if errors.Is(fetchErr, cc.ErrSecretExpired) {
194 err = secret.Refresh("")
195 usedExistingSecretValue = false
196 } else {
197 err = secret.Refresh(secret.Plain())
198 usedExistingSecretValue = true
199 }
200 if err != nil {
201 if err := secret.Refresh(""); err != nil {
202 return err
203 }
204 usedExistingSecretValue = false
205 }
206 if err := secret.Apply(ctx, sm, clusterEdgeID); err != nil {
207 auditLog.Log(secret.Type(), audit.WriteRequest, "action_by", "api", "reason", fetchErr.Error(), "clusterEdgeID", clusterEdgeID, "secretPersisted", usedExistingSecretValue)
208 return err
209 }
210 auditLog.Log(secret.Type(), audit.WriteRequest, "action_by", "api", "reason", fetchErr.Error(), "clusterEdgeID", clusterEdgeID, "version", secret.Version(), "secretPersisted", usedExistingSecretValue)
211 return nil
212 }
213
214 func (r *Resolver) rotateClusterSecrets(ctx context.Context, secretClient types.SecretManagerService, clusterEdgeID string) error {
215
216 for _, secret := range clustersecrets.List() {
217 if err := secret.Fetch(ctx, secretClient, clusterEdgeID, "latest"); err != nil {
218 return err
219 }
220 if err := r.reformatSecret(ctx, secretClient, secret, clusterEdgeID, cc.ErrSecretExpired); err != nil {
221 return err
222 }
223 clusterSecret, err := r.ClusterSecretService.FetchClusterSecret(ctx, clusterEdgeID, secret.Type())
224 if err != nil {
225 return err
226 }
227 if err := r.ClusterSecretService.UpdateClusterSecret(ctx, clusterSecret.SecretEdgeID, secret.Type(), secret.Version()); err != nil {
228 return err
229 }
230 }
231 return nil
232 }
233
234
235 func (r *Resolver) fetchClusterSecretLease(ctx context.Context, clusterEdgeID string, secretType model.ClusterSecretType) (*model.ClusterSecretLease, error) {
236 var lease model.ClusterSecretLease
237 isSecCompEnabled, err := r.GetBannerEdgeSecurityCompliance(ctx, clusterEdgeID)
238 if err != nil {
239 return nil, err
240 }
241 if !isSecCompEnabled {
242 return nil, ErrBannerOptedOutOfCompliance
243 }
244 user := middleware.ForContext(ctx)
245 auditLog := audit.New("api")
246
247 lease, err = r.ClusterSecretService.FetchLease(ctx, clusterEdgeID)
248 if err != nil {
249 return nil, err
250 }
251 auditLog.Log(secretType, audit.ReadClusterSecretLease, "action_by", user.Username, "clusterEdgeID", clusterEdgeID, "fetchClusterSecretLease", lease)
252 return &lease, nil
253 }
254
255
256 func (r *Resolver) removeUserFromClusterSecretLease(ctx context.Context, clusterEdgeID string, secretType model.ClusterSecretType, username string, removal string) (bool, error) {
257 isSecCompEnabled, err := r.GetBannerEdgeSecurityCompliance(ctx, clusterEdgeID)
258 if err != nil {
259 return false, err
260 }
261 if !isSecCompEnabled {
262 return false, ErrBannerOptedOutOfCompliance
263 }
264 user := middleware.ForContext(ctx)
265 auditLog := audit.New("api")
266 if removal == "release" {
267 if err := r.ClusterSecretService.ReleaseLease(ctx, clusterEdgeID); err != nil {
268 return false, err
269 }
270 auditLog.Log(secretType, audit.ReleaseBreakoutSession, "action_by", user.Username, "clusterEdgeID", clusterEdgeID, "releaseClusterSecretLease", true)
271 }
272 if removal == "revoke" {
273 if err := r.ClusterSecretService.RevokeLease(ctx, clusterEdgeID, username); err != nil {
274 return false, err
275 }
276 auditLog.Log(secretType, audit.RevokeBreakoutSession, "action_by", user.Username, "clusterEdgeID", clusterEdgeID, "revokeClusterSecretLease", true, "user_revoked", username)
277 }
278 leaseID, err := r.ClusterSecretService.FetchLeaseID(ctx, clusterEdgeID)
279 if err != nil {
280 return false, err
281 }
282
283 if err := r.ClusterSecretService.ExpireClusterSecrets(ctx, leaseID); err != nil {
284 return false, err
285 }
286 return true, nil
287 }
288
289
290 func (r *Resolver) fetchClusterSecretVersions(ctx context.Context, clusterEdgeID string, secretType model.ClusterSecretType) ([]*model.ClusterSecretVersionInfo, error) {
291 user := middleware.ForContext(ctx)
292 auditLog := audit.New("api")
293
294 versions, err := r.ClusterSecretService.FetchClusterSecretVersions(ctx, clusterEdgeID, secretType)
295 if err != nil {
296 return nil, err
297 }
298 auditLog.Log(secretType, audit.GetClusterSecretVersions, "action_by", user.Username, "clusterEdgeID", clusterEdgeID, "fetchClusterSecretVersions", versions)
299 return versions, nil
300 }
301
302
303 func (r *Resolver) obtainClusterSecretLease(ctx context.Context, clusterEdgeID string, secretType model.ClusterSecretType, secretClient types.SecretManagerService) error {
304 user := middleware.ForContext(ctx)
305 auditLog := audit.New("api")
306 rotateSecret, err := r.ClusterSecretService.ObtainLease(ctx, clusterEdgeID)
307 if err != nil {
308 return err
309 }
310
311 if rotateSecret {
312 if err := r.rotateClusterSecrets(ctx, secretClient, clusterEdgeID); err != nil {
313 return err
314 }
315 }
316 auditLog.Log(secretType, audit.NewBreakoutSession, "action_by", user.Username, "clusterEdgeID", clusterEdgeID, "obtainClusterSecretLease", true)
317 return nil
318 }
319
View as plain text