Prechádzať zdrojové kódy

remove hash check and create db token for gha

Alexander Belanger 4 rokov pred
rodič
commit
c803a5da07

+ 7 - 7
api/server/authn/handler.go

@@ -12,7 +12,6 @@ import (
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/auth/token"
 	"github.com/porter-dev/porter/internal/models"
-	"golang.org/x/crypto/bcrypt"
 )
 
 // AuthNFactory generates a middleware handler `AuthN`
@@ -138,12 +137,6 @@ func (authn *AuthN) verifyTokenWithNext(w http.ResponseWriter, r *http.Request,
 			return
 		}
 
-		// next, compare the secret against the hashed version
-		if err := bcrypt.CompareHashAndPassword([]byte(apiToken.SecretKey), []byte(tok.Secret)); err != nil {
-			authn.sendForbiddenError(fmt.Errorf("incorrect secret key for token %s", tok.TokenID), w, r)
-			return
-		}
-
 		authn.nextWithAPIToken(w, r, apiToken)
 	} else {
 		// otherwise we just use nextWithUser using the `iby` field for the token
@@ -156,6 +149,13 @@ func (authn *AuthN) nextWithAPIToken(w http.ResponseWriter, r *http.Request, tok
 	ctx := r.Context()
 	ctx = context.WithValue(ctx, "api_token", tok)
 
+	// add a service account user to the project: note that any calls depending on a DB lookup for the
+	// user will fail
+	ctx = context.WithValue(ctx, types.UserScope, &models.User{
+		Email:         fmt.Sprintf("%s-%d", tok.Name, tok.ProjectID),
+		EmailVerified: true,
+	})
+
 	r = r.Clone(ctx)
 	authn.next.ServeHTTP(w, r)
 }

+ 107 - 8
api/server/handlers/release/create.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net/http"
 	"strings"
+	"time"
 
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -21,6 +22,7 @@ import (
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/oauth"
 	"github.com/porter-dev/porter/internal/registry"
+	"golang.org/x/crypto/bcrypt"
 	"gopkg.in/yaml.v2"
 	"helm.sh/helm/v3/pkg/release"
 )
@@ -232,14 +234,7 @@ func createGitAction(
 		return nil, nil, fmt.Errorf("invalid formatting of repo name")
 	}
 
-	// generate porter jwt token
-	jwt, err := token.GetTokenForAPI(userID, projectID)
-
-	if err != nil {
-		return nil, nil, err
-	}
-
-	encoded, err := jwt.EncodeToken(config.TokenConf)
+	encoded, err := getToken(config, userID, projectID, clusterID, request)
 
 	if err != nil {
 		return nil, nil, err
@@ -317,6 +312,110 @@ func createGitAction(
 	return ga.ToGitActionConfigType(), workflowYAML, nil
 }
 
+func getToken(
+	config *config.Config,
+	userID, projectID, clusterID uint,
+	request *types.CreateGitActionConfigRequest,
+) (string, error) {
+	// create a policy for the token
+	policy := []*types.PolicyDocument{
+		{
+			Scope: types.ProjectScope,
+			// This token allows no API verbs at the project level, meaning child resources need to explicitly
+			// declare granted verbs and scopes. Thus we do not allow repo-scoped tokens to access things like
+			// project settings or infra.
+			Verbs: []types.APIVerb{},
+			Children: map[types.PermissionScope]*types.PolicyDocument{
+				types.ClusterScope: {
+					Scope: types.ClusterScope,
+					Verbs: types.ReadWriteVerbGroup(),
+				},
+				types.HelmRepoScope: {
+					Scope: types.HelmRepoScope,
+					Verbs: []types.APIVerb{
+						types.APIVerbGet, types.APIVerbList,
+					},
+				},
+			},
+		},
+	}
+
+	uid, err := encryption.GenerateRandomBytes(16)
+
+	if err != nil {
+		return "", err
+	}
+
+	policyBytes, err := json.Marshal(policy)
+
+	if err != nil {
+		return "", err
+	}
+
+	policyModel := &models.Policy{
+		ProjectID:       projectID,
+		UniqueID:        uid,
+		CreatedByUserID: userID,
+		Name:            strings.ToLower(fmt.Sprintf("repo-%s-token-policy", request.GitRepo)),
+		PolicyBytes:     policyBytes,
+	}
+
+	policyModel, err = config.Repo.Policy().CreatePolicy(policyModel)
+
+	if err != nil {
+		return "", err
+	}
+
+	// create the token in the database
+	tokenUID, err := encryption.GenerateRandomBytes(16)
+
+	if err != nil {
+		return "", err
+	}
+
+	secretKey, err := encryption.GenerateRandomBytes(16)
+
+	if err != nil {
+		return "", err
+	}
+
+	// hash the secret key for storage in the db
+	hashedToken, err := bcrypt.GenerateFromPassword([]byte(secretKey), 8)
+
+	if err != nil {
+		return "", err
+	}
+
+	expiresAt := time.Now().Add(time.Hour * 24 * 365)
+
+	apiToken := &models.APIToken{
+		UniqueID:        tokenUID,
+		ProjectID:       projectID,
+		CreatedByUserID: userID,
+		Expiry:          &expiresAt,
+		Revoked:         false,
+		PolicyUID:       policyModel.UniqueID,
+		PolicyName:      policyModel.Name,
+		Name:            strings.ToLower(fmt.Sprintf("repo-%s-token", request.GitRepo)),
+		SecretKey:       hashedToken,
+	}
+
+	apiToken, err = config.Repo.APIToken().CreateAPIToken(apiToken)
+
+	if err != nil {
+		return "", err
+	}
+
+	// generate porter jwt token
+	jwt, err := token.GetStoredTokenForAPI(userID, projectID, apiToken.UniqueID, secretKey)
+
+	if err != nil {
+		return "", err
+	}
+
+	return jwt.EncodeToken(config.TokenConf)
+}
+
 func createBuildConfig(
 	config *config.Config,
 	release *models.Release,