1 package services
2
3 import (
4 "context"
5 "encoding/json"
6 goerrors "errors"
7 "fmt"
8
9 iamAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/iam/v1beta1"
10 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/k8s/v1alpha1"
11 secretmanagerAPI "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/clients/generated/apis/secretmanager/v1beta1"
12 "github.com/rs/zerolog/log"
13 corev1 "k8s.io/api/core/v1"
14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15 "sigs.k8s.io/controller-runtime/pkg/client"
16
17 "edge-infra.dev/pkg/edge/api/clients"
18 "edge-infra.dev/pkg/edge/api/graph/mapper"
19 "edge-infra.dev/pkg/edge/api/graph/model"
20 "edge-infra.dev/pkg/edge/api/types"
21 chariotClientApi "edge-infra.dev/pkg/edge/chariot/client"
22 extScrt "edge-infra.dev/pkg/edge/externalsecrets"
23 "edge-infra.dev/pkg/lib/gcp/iam/roles"
24 "edge-infra.dev/pkg/lib/uuid"
25 )
26
27
28 type SecretService interface {
29 CreateSecret(ctx context.Context, cluster *model.Cluster, name, namespace string, description *string, values []*model.KeyValues) (bool, error)
30 GetSecrets(ctx context.Context, cluster *model.Cluster, namespace string, name *string) (*corev1.SecretList, error)
31 DeleteSecret(ctx context.Context, cluster *model.Cluster, name, namespace string) (bool, error)
32 GetClusterDefaultSecret(ctx context.Context, cluster *types.GkeCluster) (*corev1.Secret, error)
33 CreateExternalSecret(ctx context.Context, projectID string, name string, namespace string, values []*model.KeyValues, cluster *model.Cluster, clusterInfra *model.Cluster, secretType corev1.SecretType, k8sSecretName string) error
34 DeleteExternalSecret(ctx context.Context, name string, namespace string, projectID string, cluster *model.Cluster, clusterInfra *model.Cluster, k8sSecretName string) error
35 }
36
37 type secretService struct {
38 GkeService GkeClient
39 ChariotService ChariotService
40 GCPService GCPService
41 BQClient clients.BQClient
42 }
43
44 func (scrt *secretService) CreateExternalSecret(ctx context.Context, projectID string, name string, namespace string, values []*model.KeyValues, cluster *model.Cluster, clusterInfra *model.Cluster, secretType corev1.SecretType, k8sSecretName string) error {
45 m := make(map[string]string)
46 for _, v := range values {
47 m[v.Key] = v.Key
48 }
49
50 es := extScrt.DefaultExternalSecret().
51 Name(name).
52 K8sSecretName(k8sSecretName).
53 ProjectID(projectID)
54
55 if secretType != "" {
56 es.SetSecretType(secretType)
57 }
58
59
60 isClusterExternalSecret := namespace == ""
61 if !isClusterExternalSecret {
62 es = es.Namespace(namespace)
63 }
64
65 for k8sSecretKey, secretManagerProp := range m {
66 es = es.MapSecretFieldToK8sSecretKey(name, k8sSecretKey, secretManagerProp)
67 }
68
69 externalSecret, err := buildSecretMessage(es, isClusterExternalSecret)
70 if err != nil {
71 return err
72 }
73
74 err = scrt.sendChariotMessage(ctx, projectID, cluster, chariotClientApi.Create, externalSecret)
75 if err != nil {
76 return err
77 }
78
79 externalSecretPermission := createExternalSecretPermissions(projectID, name, cluster.ClusterEdgeID)
80 err = scrt.sendChariotMessage(ctx, projectID, clusterInfra, chariotClientApi.Create, externalSecretPermission)
81 if err != nil {
82 return err
83 }
84
85 return nil
86 }
87
88 func (scrt *secretService) DeleteExternalSecret(ctx context.Context, name string, namespace string, projectID string, cluster *model.Cluster, clusterInfra *model.Cluster, k8sSecretName string) error {
89 es := extScrt.DefaultExternalSecret().
90 Name(name).
91 K8sSecretName(k8sSecretName).
92 ProjectID(projectID)
93
94 isClusterExternalSecret := namespace == ""
95 if !isClusterExternalSecret {
96 es = es.Namespace(namespace)
97 }
98
99 externalSecret, err := buildSecretMessage(es, isClusterExternalSecret)
100 if err != nil {
101 return err
102 }
103
104 err = scrt.sendChariotMessage(ctx, projectID, cluster, chariotClientApi.Delete, externalSecret)
105 if err != nil {
106 return err
107 }
108
109 if !isClusterExternalSecret {
110 externalSecretPermission := createExternalSecretPermissions(projectID, name, cluster.ClusterEdgeID)
111 err = scrt.sendChariotMessage(ctx, projectID, clusterInfra, chariotClientApi.Delete, externalSecretPermission)
112 if err != nil {
113 return err
114 }
115 }
116 return nil
117 }
118
119 func (scrt *secretService) CreateSecret(ctx context.Context, cluster *model.Cluster, name, namespace string, _ *string, values []*model.KeyValues) (bool, error) {
120 var objects []client.Object
121 namespace = mapper.ConvertK8sName(namespace)
122 secret, err := mapper.ToCreateSecretObject(name, namespace, values)
123 if err != nil {
124 return false, err
125 }
126 secret.APIVersion = "v1"
127 secret.Type = "Opaque"
128 secret.Kind = "Secret"
129
130 objects = append(objects, secret)
131
132 err = scrt.sendChariotMessage(ctx, cluster.ProjectID, cluster, chariotClientApi.Create, objects...)
133 if err != nil {
134 return false, err
135 }
136
137 return true, nil
138 }
139
140 func (scrt *secretService) GetSecrets(ctx context.Context, cluster *model.Cluster, namespace string, name *string) (*corev1.SecretList, error) {
141 res, err := scrt.BQClient.GetKubeResource(ctx, cluster.ProjectID, cluster, mapper.GetSecrets(name, &namespace))
142
143 if err != nil {
144 return nil, err
145 }
146 var list []corev1.Secret
147
148 for i := range res {
149 secret := &corev1.Secret{}
150 err = json.Unmarshal([]byte(res[i]), secret)
151 if err != nil {
152 log.Ctx(ctx).Err(err).Msg("Unable to unmarshal secret release resource")
153 }
154 list = append(list, *secret)
155 }
156
157 if len(list) > 0 {
158 return &corev1.SecretList{Items: list}, nil
159 }
160 return nil, goerrors.New("error fetching Secret")
161 }
162
163 func (scrt *secretService) DeleteSecret(ctx context.Context, cluster *model.Cluster, name, namespace string) (bool, error) {
164 var object []client.Object
165 namespace = mapper.ConvertK8sName(namespace)
166
167 var emptyvals []*model.KeyValues
168 secret, err := mapper.ToCreateSecretObject(name, namespace, emptyvals)
169
170 if err != nil {
171 return false, err
172 }
173
174 secret.APIVersion = "v1"
175 secret.Type = "Opaque"
176 secret.Kind = "Secret"
177 object = append(object, secret)
178
179 err = scrt.sendChariotMessage(ctx, cluster.ProjectID, cluster, chariotClientApi.Delete, object...)
180 if err != nil {
181 return false, err
182 }
183
184 return true, nil
185 }
186
187 func (scrt *secretService) GetClusterDefaultSecret(ctx context.Context, cluster *types.GkeCluster) (*corev1.Secret, error) {
188 cl, err := scrt.GkeService.GetRuntimeClient(ctx, cluster)
189 if err != nil {
190 return nil, err
191 }
192
193 sa := &corev1.ServiceAccount{}
194 if err := cl.Get(ctx, client.ObjectKey{Namespace: "default", Name: "default"}, sa); err != nil {
195 log.Ctx(ctx).Err(err).Msg("error in retrieving service account")
196 return nil, err
197 }
198
199 secret := &corev1.Secret{}
200 if err := cl.Get(ctx, client.ObjectKey{Namespace: "default", Name: sa.Secrets[0].Name}, secret); err != nil {
201 log.Ctx(ctx).Err(err).Msg("error in retrieving secret")
202 return nil, err
203 }
204
205 return secret, nil
206 }
207
208 func (scrt *secretService) sendChariotMessage(ctx context.Context, projectID string, cluster *model.Cluster, operation chariotClientApi.Operation, object ...client.Object) error {
209 chariotMessage, err := chariotClientApi.
210 NewChariotMessage().
211 SetBanner(projectID).
212 SetOperation(operation).
213 SetOwner(ComponentOwner).
214 AddK8sBase64Object(object...)
215
216 if err != nil {
217 return err
218 }
219
220 if err := scrt.ChariotService.CheckClusterAndInvokeChariotPubsub(ctx, cluster, chariotMessage, nil); err != nil {
221 return fmt.Errorf("error calling chariot v2: %w", err)
222 }
223 return nil
224 }
225
226 func buildClusterExternalSecret(es *extScrt.ExternalSecret) (client.Object, error) {
227 externalSecret, err := es.BuildClusterExternalSecret()
228 if err != nil {
229 return nil, err
230 }
231 return externalSecret, nil
232 }
233
234 func buildExternalSecret(es *extScrt.ExternalSecret) (client.Object, error) {
235 externalSecret, err := es.Build()
236 if err != nil {
237 return nil, err
238 }
239 return externalSecret, nil
240 }
241
242 func buildSecretMessage(es *extScrt.ExternalSecret, isClusterExternalSecret bool) (client.Object, error) {
243 if isClusterExternalSecret {
244 return buildClusterExternalSecret(es)
245 }
246 return buildExternalSecret(es)
247 }
248
249 func createExternalSecretPermissions(projectID, name, namespace string) *iamAPI.IAMPolicyMember {
250 member := fmt.Sprintf("serviceAccount:ext-sec-%s@%s.iam.gserviceaccount.com", uuid.FromUUID(namespace).Hash(), projectID)
251 return &iamAPI.IAMPolicyMember{
252 ObjectMeta: metav1.ObjectMeta{
253 Name: fmt.Sprintf("essa-%s", name),
254 Namespace: namespace,
255 },
256 TypeMeta: metav1.TypeMeta{
257 APIVersion: iamAPI.IAMPolicyMemberGVK.GroupVersion().String(),
258 Kind: iamAPI.IAMPolicyMemberGVK.Kind,
259 },
260 Spec: iamAPI.IAMPolicyMemberSpec{
261 Member: &member,
262 ResourceRef: v1alpha1.IAMResourceRef{
263 APIVersion: secretmanagerAPI.SecretManagerSecretGVK.GroupVersion().String(),
264 Kind: secretmanagerAPI.SecretManagerSecretGVK.Kind,
265 External: fmt.Sprintf("projects/%s/secrets/%s", projectID, name),
266 },
267 Role: roles.SecretAccessor,
268 },
269 }
270 }
271
272 func NewSecretService(gkeService GkeClient, chariotService ChariotService, gcpService GCPService, bqClient clients.BQClient) *secretService {
273 return &secretService{
274 GkeService: gkeService,
275 ChariotService: chariotService,
276 GCPService: gcpService,
277 BQClient: bqClient,
278 }
279 }
280
View as plain text