1 package edgebsl
2
3 import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "net/http"
11 "os"
12 "strconv"
13 "strings"
14 "time"
15
16 "github.com/gin-contrib/requestid"
17 "github.com/gin-gonic/gin"
18
19 server "edge-infra.dev/pkg/x/tonic"
20
21 "edge-infra.dev/pkg/edge/api/middleware"
22 "edge-infra.dev/pkg/lib/gcp/cloudsql"
23
24 "github.com/go-logr/logr"
25
26 "edge-infra.dev/pkg/edge/api/graph/model"
27 "edge-infra.dev/pkg/edge/api/services"
28
29 btypes "edge-infra.dev/pkg/edge/api/types"
30 "edge-infra.dev/pkg/edge/bsl"
31 bslMetrics "edge-infra.dev/pkg/edge/bsl-reconciler/metrics"
32 "edge-infra.dev/pkg/edge/constants"
33 secretMgrApi "edge-infra.dev/pkg/lib/gcp/secretmanager"
34 gcputils "edge-infra.dev/pkg/lib/gcp/utils"
35 logger "edge-infra.dev/pkg/lib/logging"
36 "edge-infra.dev/pkg/lib/runtime/manager"
37 "edge-infra.dev/pkg/lib/runtime/metrics"
38 )
39
40 type BSL struct {
41 logger logr.Logger
42 metrics *bslMetrics.Metrics
43 secretManagerProvider btypes.SecretManagerFunc
44 bslConfig BslConfig
45 db *sql.DB
46 TenantService services.TenantService
47 }
48
49 const (
50 DefaultReconcileInterval = "60"
51 alreadyExistsLogFormat = "%s for org %s"
52 SubscriptionOrgNameAttrKey = "OnboardedOrganization"
53
54
55 Username = "username"
56 Password = "password"
57 )
58
59 func RunManager(ctx context.Context) error {
60 log := logger.NewLogger().Logger
61
62 cfg, _, err := NewConfig(os.Args)
63 if err != nil {
64 log.Error(err, "failed to parse startup configuration")
65 os.Exit(1)
66 }
67
68 secretManagerProvider := func(ctx context.Context, projectID string) (btypes.SecretManagerService, error) {
69 return secretMgrApi.NewWithOptions(ctx, projectID)
70 }
71
72 startManager(ctx, cfg, secretManagerProvider, log, metrics.DefaultBindAddress)
73
74 return nil
75 }
76
77 func startManager(ctx context.Context, cfg BslConfig, secretManagerProvider btypes.SecretManagerFunc, log logr.Logger, port string) {
78 mgr, err := manager.New(manager.Options{MetricsBindAddress: port})
79 if err != nil {
80 log.Error(err, "failed to create a new manager")
81 os.Exit(1)
82 }
83
84 dbConnection, err := DB(cfg, log)
85 if err != nil {
86 log.Error(err, "An error occurred creating sql db connection")
87 os.Exit(1)
88 }
89
90 bslOperations := New(secretManagerProvider, log, cfg, dbConnection)
91 if err != nil {
92 log.Error(err, "failed to create a bsl ops")
93 os.Exit(1)
94 }
95
96 if err := mgr.Add(bslOperations); err != nil {
97 log.Error(err, "failed to add bsl ops")
98 os.Exit(1)
99 }
100
101 bslServer := server.NewWithOptions(cfg.Port, cfg.Mode).
102 SetLogger(log).
103 SetMiddlewares(middleware.SetRequestContext(), requestid.New(), middleware.RequestLogger(log), gin.Recovery(), server.CorsMiddleware()).
104 WithHealthRoute().
105 WithReadyRoute().
106 SetRoutes(server.Route{
107 Path: "/new-org-created/",
108 Action: http.MethodPost,
109 Handlers: []gin.HandlerFunc{
110 gin.BasicAuth(gin.Accounts{
111 cfg.BasicAuthCreds.Username: cfg.BasicAuthCreds.Password,
112 }),
113 func(c *gin.Context) {
114 payload, err := io.ReadAll(c.Request.Body)
115 if err != nil {
116 log.Error(err, "Error reading BSL new org payload data")
117 } else {
118 payloadJSONStr := string(payload)
119 log.Info(fmt.Sprintf("BSL Organization Created: %s", payloadJSONStr))
120
121 bslSubscriptionPostPayload := &BSLSubscriptionPayload{}
122
123 if err := json.Unmarshal(payload, bslSubscriptionPostPayload); err != nil {
124 log.Error(err, "Error de-coding BSL new org JSON payload data")
125 }
126
127 var OrgName string
128
129 for _, val := range bslSubscriptionPostPayload.Attributes {
130 if val.Key == SubscriptionOrgNameAttrKey {
131 OrgName = val.Value
132 }
133 }
134
135 orgToProcess := &AllEdgeOrgsPageContent{
136 OrganizationName: OrgName,
137 ID: "",
138 DisplayName: "",
139 Parent: true,
140 }
141
142 tenantsNames := bslOperations.GetTenantsList(ctx)
143 sctmrg, _ := secretMgrApi.NewWithOptions(ctx, cfg.TopLevelProjectID)
144
145 log.Info("Starting org processing for: ", orgToProcess.OrganizationName)
146 err := bslOperations.processEdgeOrganization(c, orgToProcess, sctmrg, tenantsNames)
147
148 if err != nil {
149 log.Error(err, "Error processing organization")
150 }
151
152 c.IndentedJSON(http.StatusOK, orgToProcess)
153 }
154 }},
155 }).With404Route()
156
157 if err := mgr.Add(bslServer); err != nil {
158 log.Error(err, "failed to add bsl server")
159 os.Exit(1)
160 }
161
162 if err := mgr.Start(ctx); err != nil {
163 log.Error(err, "bsl errored")
164 os.Exit(1)
165 }
166 }
167
168 func New(secretManagerProvider btypes.SecretManagerFunc, log logr.Logger, cfg BslConfig, dbConnection *sql.DB) *BSL {
169 m := bslMetrics.NewMetrics()
170 m.InitMetrics()
171 b := &BSL{
172 logger: log,
173 metrics: m,
174 secretManagerProvider: secretManagerProvider,
175 bslConfig: cfg,
176 db: dbConnection,
177 TenantService: services.NewTenantService(dbConnection, nil, nil),
178 }
179 return b
180 }
181
182 func (b *BSL) Start(ctx context.Context) error {
183 reconcileInterval, err := strconv.Atoi(b.bslConfig.BslReconcilesInterval)
184 if err != nil {
185 return fmt.Errorf("unable to convert BSL_RECONCILE_INTERVAL, %v", err)
186 }
187
188 if err = b.Reconcile(ctx); err != nil {
189 return fmt.Errorf("unable to reconcile, %v", err)
190 }
191
192 for {
193 select {
194 case <-time.After(time.Duration(reconcileInterval) * time.Minute):
195 err = b.Reconcile(ctx)
196 if err != nil {
197 return fmt.Errorf("unable to reconcile, %v", err)
198 }
199 case <-ctx.Done():
200 return nil
201 }
202 }
203 }
204
205
206
207 func (b *BSL) Reconcile(ctx context.Context) error {
208 log := b.logger.WithValues("bsl operator", "reconcile")
209 b.metrics.ReconcileInc()
210
211 tenantsNames := b.GetTenantsList(ctx)
212
213 allOrgsList, err := b.bslConfig.GetAllEdgeOrgs(ctx)
214 if err != nil {
215 log.Error(err, "failed to get bsl edge orgs")
216 b.metrics.ErrorInc("bsl_error", "", err.Error())
217 return err
218 }
219
220 sm, err := b.secretManagerProvider(ctx, b.bslConfig.TopLevelProjectID)
221 if err != nil {
222 log.Error(err, "fail to create new secret manager api", "project", b.bslConfig.TopLevelProjectID)
223 b.metrics.ErrorInc("kube_error", "", err.Error())
224 return err
225 }
226
227 defer gcputils.CloseConnection(ctx, sm)
228
229 return b.processAllEdgeOrganizations(ctx, allOrgsList, sm, tenantsNames)
230 }
231
232 func (b *BSL) GetTenantsList(ctx context.Context) map[string]struct{} {
233 log := b.logger.WithValues("bsl operator", "Get Tenants List")
234
235 tenants, err := b.TenantService.List(ctx)
236
237 if err != nil {
238 log.Error(err, "An error occurred getting tenants info")
239 b.metrics.ErrorInc("sql_error", "", err.Error())
240 }
241
242 tenantsNames := make(map[string]struct{}, 0)
243 for _, tenant := range tenants {
244 tenantsNames[tenant.OrgName] = struct{}{}
245 }
246
247 return tenantsNames
248 }
249
250 func (b *BSL) CreateUserAndSecret(ctx context.Context, sm btypes.SecretManagerService, orgName string, bslConfig *BslConfig, log logr.Logger, secretName string, bffuser bool) error {
251
252 _, err := sm.GetSecret(ctx, secretName)
253 if err != nil && strings.Contains(err.Error(), "not found") {
254 b.metrics.NewOrgInc(secretName)
255 err = nil
256 username := newBSLUsername(6, bffuser)
257 password, bslerr := bslConfig.CreateBSLUser(ctx, orgName, username)
258 if bslerr != nil {
259 log.Error(bslerr, LogWithSecret(secretName, "failed to create bsl user"))
260 return fmt.Errorf("fail to create new bff user and password, error; %v", bslerr)
261 }
262
263
264 res, err := bslConfig.CreateBSLUserAccessKey(ctx, orgName, username)
265 if err != nil {
266 log.Error(err, LogWithSecret(secretName, "failed to create access key for user"))
267 return fmt.Errorf("failed to create access key for user, error; %v", bslerr)
268 }
269 if err := createUpdateSecretManagerSecret(ctx, sm, secretName, username, password, res.SharedKey, res.SecretKey); err != nil {
270 log.Error(err, LogWithSecret(secretName, "failed to create bsl org secret in secret manager"))
271 return err
272 }
273
274 if bffuser {
275
276 if err := bslConfig.AssignBSLUserToGroup(ctx, orgName, string(model.RoleEdgeSuperAdmin), username); err != nil {
277 if !errors.Is(err, ErrorResourceAlreadyExists) {
278 log.Error(err, LogWithSecret(secretName, "failed to assign bsl user to EDGE_SUPER_ADMIN"))
279 } else {
280 log.Info(LogWithSecret(secretName, "failed to assign bsl user to EDGE_SUPER_ADMIN, the user is already assigned to EDGE_SUPER_ADMIN"))
281 }
282 }
283 } else {
284
285 if err := bslConfig.AssignBSLUserToGroup(ctx, orgName, string(model.RoleEdgeOrgAdmin), username); err != nil {
286 if !errors.Is(err, ErrorResourceAlreadyExists) {
287 log.Error(err, LogWithSecret(secretName, "failed to assign bsl user to EDGE_ORG_ADMIN"))
288 } else {
289 log.Info(LogWithSecret(secretName, "failed to assign bsl user to EDGE_ORG_ADMIN, the user is already assigned to EDGE_ORG_ADMIN"))
290 }
291 }
292 }
293 }
294 return err
295 }
296
297 func createUpdateSecretManagerSecret(ctx context.Context, sm btypes.SecretManagerService, name, username, password, sharedKey, secretKey string) error {
298 data, err := json.Marshal(map[string]string{
299 Username: username,
300 Password: password,
301 bsl.SharedKey: sharedKey,
302 bsl.SecretKey: secretKey,
303 })
304 if err != nil {
305 return err
306 }
307 labels := map[string]string{
308 secretMgrApi.SecretLabel: "platform",
309 secretMgrApi.SecretTypeLabel: "bsl-secret",
310 secretMgrApi.SecretOwnerLabel: "edge",
311 secretMgrApi.SecretNamespaceSelectorLabel: string(constants.PlatformNamespaceSelector),
312 }
313 return sm.AddSecret(ctx, name, data, labels, true, nil, "")
314 }
315
316 func DB(config BslConfig, log logr.Logger) (*sql.DB, error) {
317 dbConnection, err := cloudsql.GCPPostgresConnection(config.SQL.ConnectionName).
318 DBName(config.SQL.DatabaseName).
319 Password(config.SQL.Password).
320 Username(config.SQL.User).
321 NewConnection()
322 if err != nil {
323 log.Error(err, "failed to create sql db connection")
324 }
325 return dbConnection, err
326 }
327
View as plain text