diff --git a/internal/delivery/http/cluster.go b/internal/delivery/http/cluster.go index bd49b31b..8cc9aa92 100644 --- a/internal/delivery/http/cluster.go +++ b/internal/delivery/http/cluster.go @@ -2,6 +2,7 @@ package http import ( "fmt" + "github.com/openinfradev/tks-api/internal/helper" "net/http" "github.com/google/uuid" @@ -16,12 +17,14 @@ import ( ) type ClusterHandler struct { - usecase usecase.IClusterUsecase + usecase usecase.IClusterUsecase + userUsecase usecase.IUserUsecase } func NewClusterHandler(h usecase.Usecase) *ClusterHandler { return &ClusterHandler{ - usecase: h.Cluster, + usecase: h.Cluster, + userUsecase: h.User, } } @@ -169,6 +172,7 @@ func (h *ClusterHandler) CreateCluster(w http.ResponseWriter, r *http.Request) { dto.ClusterType = domain.ClusterType_USER dto.SetDefaultConf() + dto.OpaGatekeeperPassword = helper.GenerateRandomPassword(16) //txHandle := r.Context().Value("txHandle").(*gorm.DB) clusterId := domain.ClusterId("") @@ -188,7 +192,13 @@ func (h *ClusterHandler) CreateCluster(w http.ResponseWriter, r *http.Request) { ErrorJSON(w, r, err) return } + } + // create OPA Gatekeeper account + err = h.userUsecase.CreateReservedAccount(r.Context(), clusterId.String()+string(usecase.OPAGatekeeperReservedAccountIdSuffix), dto.OpaGatekeeperPassword) + if err != nil { + ErrorJSON(w, r, err) + return } var out domain.CreateClusterResponse diff --git a/internal/helper/password.go b/internal/helper/password.go index 0ec16a3a..13c186e0 100644 --- a/internal/helper/password.go +++ b/internal/helper/password.go @@ -29,6 +29,16 @@ func GenerateRandomString(length int) string { return string(b) } +func GenerateRandomPassword(length int) string { + // generate a random password with string of letters, digits, and special characters + var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()") + b := make([]rune, length) + for i := range b { + b[i] = randomRune(letters) + } + return string(b) +} + func randomRune(chars []rune) rune { n, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) if err != nil { diff --git a/internal/middleware/auth/authorizer/abac.go b/internal/middleware/auth/authorizer/abac.go new file mode 100644 index 00000000..f45b5448 --- /dev/null +++ b/internal/middleware/auth/authorizer/abac.go @@ -0,0 +1,45 @@ +package authorizer + +import ( + "github.com/openinfradev/tks-api/internal/middleware/auth/request" + "github.com/openinfradev/tks-api/internal/repository" + "github.com/openinfradev/tks-api/pkg/log" + "net/http" +) + +var ( + userBasedAccessControl []func(r *http.Request) bool +) + +func init() { + userBasedAccessControl = append(userBasedAccessControl, OpaGatekeeper) +} + +func ABACFilter(handler http.Handler, repo repository.Repository) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for _, f := range userBasedAccessControl { + if !f(r) { + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + } + + handler.ServeHTTP(w, r) + }) +} + +func OpaGatekeeper(r *http.Request) bool { + requestUserInfo, ok := request.UserFrom(r.Context()) + if !ok { + log.Errorf(r.Context(), "user not found") + return false + } + + _ = requestUserInfo + // ToDo: Add only API endpoints for OPA Gatekeeper + //if strings.HasSuffix(requestUserInfo.GetAccountId(), string(usecase.OPAGatekeeperReservedAccountIdSuffix)) { + // // Allow restricted API from OPA Gatekeeper + //} + + return true +} diff --git a/internal/middleware/auth/authorizer/authorizer.go b/internal/middleware/auth/authorizer/authorizer.go index 1ce71727..2ffe2e6d 100644 --- a/internal/middleware/auth/authorizer/authorizer.go +++ b/internal/middleware/auth/authorizer/authorizer.go @@ -18,6 +18,7 @@ func NewDefaultAuthorization(repo repository.Repository) *defaultAuthorization { d := &defaultAuthorization{ repo: repo, } + d.addFilters(ABACFilter) d.addFilters(PasswordFilter) //d.addFilters(RBACFilter) //d.addFilters(RBACFilterWithEndpoint) diff --git a/internal/model/cluster.go b/internal/model/cluster.go index 0166546a..1b1888a2 100644 --- a/internal/model/cluster.go +++ b/internal/model/cluster.go @@ -44,6 +44,7 @@ type Cluster struct { UpdatorId *uuid.UUID `gorm:"type:uuid"` Updator User `gorm:"foreignKey:UpdatorId"` Policies []Policy `gorm:"many2many:policy_target_clusters"` + OpaGatekeeperPassword string `gorm:"-:all"` } func (m *Cluster) SetDefaultConf() { diff --git a/internal/usecase/cluster.go b/internal/usecase/cluster.go index ce7574e8..3e777df2 100644 --- a/internal/usecase/cluster.go +++ b/internal/usecase/cluster.go @@ -199,6 +199,7 @@ func (u *ClusterUsecase) Create(ctx context.Context, dto model.Cluster) (cluster "base_repo_branch=" + viper.GetString("revision"), "keycloak_url=" + viper.GetString("keycloak-address"), "policy_ids=" + strings.Join(dto.PolicyIds, ","), + "opa_gatekeeper_password=" + dto.OpaGatekeeperPassword, }, }) if err != nil { diff --git a/internal/usecase/user.go b/internal/usecase/user.go index 35be8fb9..855d4389 100644 --- a/internal/usecase/user.go +++ b/internal/usecase/user.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "strings" "github.com/Nerzal/gocloak/v13" "github.com/google/uuid" @@ -18,6 +19,12 @@ import ( "github.com/pkg/errors" ) +type ReservedAccountIdSuffix string + +const ( + OPAGatekeeperReservedAccountIdSuffix ReservedAccountIdSuffix = "-opa-gatekeeper" +) + type IUserUsecase interface { CreateAdmin(ctx context.Context, user *model.User) (*model.User, error) DeleteAdmin(ctx context.Context, organizationId string) error @@ -46,6 +53,7 @@ type IUserUsecase interface { UpdateByAccountIdByAdmin(ctx context.Context, user *model.User) (*model.User, error) ListUsersByRole(ctx context.Context, organizationId string, roleId string, pg *pagination.Pagination) (*[]model.User, error) + CreateReservedAccount(ctx context.Context, accountId string, password string) error } type UserUsecase struct { @@ -448,7 +456,6 @@ func (u *UserUsecase) Create(ctx context.Context, user *model.User) (*model.User if user.ID, err = uuid.Parse(userUuidStr); err != nil { return nil, err } - // Create user in DB resUser, err := u.userRepository.Create(ctx, user) //resUser, err := u.userRepository.Create(ctx, userUuid, user.AccountId, user.Name, user.Email, @@ -537,3 +544,24 @@ func NewUserUsecase(r repository.Repository, kc keycloak.IKeycloak) IUserUsecase organizationRepository: r.Organization, } } + +func (u *UserUsecase) CreateReservedAccount(ctx context.Context, accountId string, password string) error { + if strings.HasSuffix(accountId, string(OPAGatekeeperReservedAccountIdSuffix)) { + _, err := u.kc.CreateUser(ctx, accountId, &gocloak.User{ + Username: gocloak.StringP(accountId), + Credentials: &[]gocloak.CredentialRepresentation{ + { + Type: gocloak.StringP("password"), + Value: gocloak.StringP(password), + Temporary: gocloak.BoolP(false), + }, + }, + }) + + if err != nil { + return err + } + } + + return nil +}