@@ -23,7 +23,6 @@ import ( |
| 23 | 23 | "github.com/tenseleyFlow/shithub/internal/actions/finalize" |
| 24 | 24 | "github.com/tenseleyFlow/shithub/internal/actions/runnerlabels" |
| 25 | 25 | "github.com/tenseleyFlow/shithub/internal/actions/runnertoken" |
| 26 | | - "github.com/tenseleyFlow/shithub/internal/actions/secrets" |
| 27 | 26 | actionsdb "github.com/tenseleyFlow/shithub/internal/actions/sqlc" |
| 28 | 27 | "github.com/tenseleyFlow/shithub/internal/auth/runnerjwt" |
| 29 | 28 | "github.com/tenseleyFlow/shithub/internal/checks" |
@@ -93,7 +92,7 @@ func (h *Handlers) runnerHeartbeat(w http.ResponseWriter, r *http.Request) { |
| 93 | 92 | return |
| 94 | 93 | } |
| 95 | 94 | |
| 96 | | - job, steps, claimed, err := h.claimRunnerJob(r.Context(), runner.ID, labels, int32(capacity)) |
| 95 | + job, steps, resolvedSecrets, claimed, err := h.claimRunnerJob(r.Context(), runner.ID, labels, int32(capacity)) |
| 97 | 96 | if err != nil { |
| 98 | 97 | h.d.Logger.ErrorContext(r.Context(), "runner heartbeat claim failed", "runner_id", runner.ID, "error", err) |
| 99 | 98 | writeAPIError(w, http.StatusInternalServerError, "runner heartbeat failed") |
@@ -118,12 +117,6 @@ func (h *Handlers) runnerHeartbeat(w http.ResponseWriter, r *http.Request) { |
| 118 | 117 | } |
| 119 | 118 | metrics.ActionsRunnerHeartbeatsTotal.WithLabelValues("claimed").Inc() |
| 120 | 119 | metrics.ActionsRunnerJWTTotal.WithLabelValues("issued").Inc() |
| 121 | | - resolvedSecrets, err := h.resolveVisibleSecrets(r.Context(), job.RepoID) |
| 122 | | - if err != nil { |
| 123 | | - h.d.Logger.ErrorContext(r.Context(), "runner secret resolution failed", "repo_id", job.RepoID, "job_id", job.ID, "error", err) |
| 124 | | - writeAPIError(w, http.StatusInternalServerError, "runner secret resolution failed") |
| 125 | | - return |
| 126 | | - } |
| 127 | 120 | writeJSON(w, http.StatusOK, presentRunnerClaim(job, steps, resolvedSecrets, token, time.Unix(claims.Exp, 0))) |
| 128 | 121 | } |
| 129 | 122 | |
@@ -169,11 +162,11 @@ func (h *Handlers) claimRunnerJob( |
| 169 | 162 | runnerID int64, |
| 170 | 163 | labels []string, |
| 171 | 164 | capacity int32, |
| 172 | | -) (actionsdb.ClaimQueuedWorkflowJobRow, []actionsdb.ListRunnerStepsForJobRow, bool, error) { |
| 165 | +) (actionsdb.ClaimQueuedWorkflowJobRow, []actionsdb.ListRunnerStepsForJobRow, map[string]string, bool, error) { |
| 173 | 166 | q := actionsdb.New() |
| 174 | 167 | tx, err := h.d.Pool.Begin(ctx) |
| 175 | 168 | if err != nil { |
| 176 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 169 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 177 | 170 | } |
| 178 | 171 | committed := false |
| 179 | 172 | defer func() { |
@@ -183,11 +176,11 @@ func (h *Handlers) claimRunnerJob( |
| 183 | 176 | }() |
| 184 | 177 | |
| 185 | 178 | if _, err := q.LockRunnerByID(ctx, tx, runnerID); err != nil { |
| 186 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 179 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 187 | 180 | } |
| 188 | 181 | running, err := q.CountRunningJobsForRunner(ctx, tx, runnerID) |
| 189 | 182 | if err != nil { |
| 190 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 183 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 191 | 184 | } |
| 192 | 185 | if running >= capacity { |
| 193 | 186 | if _, err := q.HeartbeatRunner(ctx, tx, actionsdb.HeartbeatRunnerParams{ |
@@ -196,13 +189,13 @@ func (h *Handlers) claimRunnerJob( |
| 196 | 189 | Capacity: capacity, |
| 197 | 190 | Status: actionsdb.WorkflowRunnerStatusBusy, |
| 198 | 191 | }); err != nil { |
| 199 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 192 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 200 | 193 | } |
| 201 | 194 | if err := tx.Commit(ctx); err != nil { |
| 202 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 195 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 203 | 196 | } |
| 204 | 197 | committed = true |
| 205 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, nil |
| 198 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, nil |
| 206 | 199 | } |
| 207 | 200 | |
| 208 | 201 | job, err := q.ClaimQueuedWorkflowJob(ctx, tx, actionsdb.ClaimQueuedWorkflowJobParams{ |
@@ -211,7 +204,7 @@ func (h *Handlers) claimRunnerJob( |
| 211 | 204 | }) |
| 212 | 205 | if err != nil { |
| 213 | 206 | if !errors.Is(err, pgx.ErrNoRows) { |
| 214 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 207 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 215 | 208 | } |
| 216 | 209 | if _, err := q.HeartbeatRunner(ctx, tx, actionsdb.HeartbeatRunnerParams{ |
| 217 | 210 | ID: runnerID, |
@@ -219,20 +212,27 @@ func (h *Handlers) claimRunnerJob( |
| 219 | 212 | Capacity: capacity, |
| 220 | 213 | Status: actionsdb.WorkflowRunnerStatusIdle, |
| 221 | 214 | }); err != nil { |
| 222 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 215 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 223 | 216 | } |
| 224 | 217 | if err := tx.Commit(ctx); err != nil { |
| 225 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 218 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 226 | 219 | } |
| 227 | 220 | committed = true |
| 228 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, nil |
| 221 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, nil |
| 229 | 222 | } |
| 230 | 223 | if err := q.MarkWorkflowRunRunning(ctx, tx, job.RunID); err != nil { |
| 231 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 224 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 232 | 225 | } |
| 233 | 226 | steps, err := q.ListRunnerStepsForJob(ctx, tx, job.ID) |
| 234 | 227 | if err != nil { |
| 235 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 228 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 229 | + } |
| 230 | + resolvedSecrets, err := h.resolveVisibleSecretsFromDB(ctx, tx, job.RepoID) |
| 231 | + if err != nil { |
| 232 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 233 | + } |
| 234 | + if err := h.storeJobSecretMaskSnapshot(ctx, tx, job.ID, secretMaskValues(resolvedSecrets)); err != nil { |
| 235 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 236 | 236 | } |
| 237 | 237 | status := actionsdb.WorkflowRunnerStatusIdle |
| 238 | 238 | if running+1 >= capacity { |
@@ -244,13 +244,13 @@ func (h *Handlers) claimRunnerJob( |
| 244 | 244 | Capacity: capacity, |
| 245 | 245 | Status: status, |
| 246 | 246 | }); err != nil { |
| 247 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 247 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 248 | 248 | } |
| 249 | 249 | if err := tx.Commit(ctx); err != nil { |
| 250 | | - return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, false, err |
| 250 | + return actionsdb.ClaimQueuedWorkflowJobRow{}, nil, nil, false, err |
| 251 | 251 | } |
| 252 | 252 | committed = true |
| 253 | | - return job, steps, true, nil |
| 253 | + return job, steps, resolvedSecrets, true, nil |
| 254 | 254 | } |
| 255 | 255 | |
| 256 | 256 | type runnerJobAuth struct { |
@@ -347,7 +347,7 @@ func (h *Handlers) runnerJobLogs(w http.ResponseWriter, r *http.Request) { |
| 347 | 347 | writeAPIError(w, http.StatusBadRequest, "chunk must be between 1 and 524288 bytes") |
| 348 | 348 | return |
| 349 | 349 | } |
| 350 | | - values, err := h.logMaskValues(r.Context(), auth.Claims.RepoID) |
| 350 | + values, err := h.jobSecretMaskValues(r.Context(), auth.Job.ID, auth.Claims.RepoID) |
| 351 | 351 | if err != nil { |
| 352 | 352 | h.d.Logger.ErrorContext(r.Context(), "runner log mask resolution failed", "repo_id", auth.Claims.RepoID, "job_id", auth.Claims.JobID, "error", err) |
| 353 | 353 | writeAPIError(w, http.StatusInternalServerError, "log mask resolution failed") |
@@ -865,22 +865,30 @@ func (h *Handlers) runnerJobCancelCheck(w http.ResponseWriter, r *http.Request) |
| 865 | 865 | }) |
| 866 | 866 | } |
| 867 | 867 | |
| 868 | +type secretResolutionDB interface { |
| 869 | + actionsdb.DBTX |
| 870 | + reposdb.DBTX |
| 871 | +} |
| 872 | + |
| 868 | 873 | func (h *Handlers) resolveVisibleSecrets(ctx context.Context, repoID int64) (map[string]string, error) { |
| 874 | + return h.resolveVisibleSecretsFromDB(ctx, h.d.Pool, repoID) |
| 875 | +} |
| 876 | + |
| 877 | +func (h *Handlers) resolveVisibleSecretsFromDB(ctx context.Context, db secretResolutionDB, repoID int64) (map[string]string, error) { |
| 869 | 878 | if h.d.SecretBox == nil { |
| 870 | 879 | return nil, nil |
| 871 | 880 | } |
| 872 | | - repo, err := reposdb.New().GetRepoByID(ctx, h.d.Pool, repoID) |
| 881 | + repo, err := reposdb.New().GetRepoByID(ctx, db, repoID) |
| 873 | 882 | if err != nil { |
| 874 | 883 | return nil, err |
| 875 | 884 | } |
| 876 | | - store := secrets.Deps{Pool: h.d.Pool, Box: h.d.SecretBox, Logger: h.d.Logger} |
| 877 | 885 | out := map[string]string{} |
| 878 | 886 | if repo.OwnerOrgID.Valid { |
| 879 | | - if err := h.mergeSecrets(ctx, store, secrets.OrgScope(repo.OwnerOrgID.Int64), out); err != nil { |
| 887 | + if err := h.mergeOrgSecrets(ctx, db, repo.OwnerOrgID.Int64, out); err != nil { |
| 880 | 888 | return nil, err |
| 881 | 889 | } |
| 882 | 890 | } |
| 883 | | - if err := h.mergeSecrets(ctx, store, secrets.RepoScope(repo.ID), out); err != nil { |
| 891 | + if err := h.mergeRepoSecrets(ctx, db, repo.ID, out); err != nil { |
| 884 | 892 | return nil, err |
| 885 | 893 | } |
| 886 | 894 | if len(out) == 0 { |
@@ -889,13 +897,44 @@ func (h *Handlers) resolveVisibleSecrets(ctx context.Context, repoID int64) (map |
| 889 | 897 | return out, nil |
| 890 | 898 | } |
| 891 | 899 | |
| 892 | | -func (h *Handlers) mergeSecrets(ctx context.Context, store secrets.Deps, scope secrets.Scope, out map[string]string) error { |
| 893 | | - items, err := store.List(ctx, scope) |
| 900 | +func (h *Handlers) mergeRepoSecrets(ctx context.Context, db actionsdb.DBTX, repoID int64, out map[string]string) error { |
| 901 | + q := actionsdb.New() |
| 902 | + items, err := q.ListRepoSecrets(ctx, db, pgtype.Int8{Int64: repoID, Valid: true}) |
| 894 | 903 | if err != nil { |
| 895 | 904 | return err |
| 896 | 905 | } |
| 897 | 906 | for _, item := range items { |
| 898 | | - plaintext, err := store.Get(ctx, scope, item.Name) |
| 907 | + row, err := q.GetRepoSecret(ctx, db, actionsdb.GetRepoSecretParams{ |
| 908 | + RepoID: pgtype.Int8{Int64: repoID, Valid: true}, |
| 909 | + Name: item.Name, |
| 910 | + }) |
| 911 | + if err != nil { |
| 912 | + return err |
| 913 | + } |
| 914 | + plaintext, err := h.d.SecretBox.Open(row.Ciphertext, row.Nonce) |
| 915 | + if err != nil { |
| 916 | + return err |
| 917 | + } |
| 918 | + out[item.Name] = string(plaintext) |
| 919 | + } |
| 920 | + return nil |
| 921 | +} |
| 922 | + |
| 923 | +func (h *Handlers) mergeOrgSecrets(ctx context.Context, db actionsdb.DBTX, orgID int64, out map[string]string) error { |
| 924 | + q := actionsdb.New() |
| 925 | + items, err := q.ListOrgSecrets(ctx, db, pgtype.Int8{Int64: orgID, Valid: true}) |
| 926 | + if err != nil { |
| 927 | + return err |
| 928 | + } |
| 929 | + for _, item := range items { |
| 930 | + row, err := q.GetOrgSecret(ctx, db, actionsdb.GetOrgSecretParams{ |
| 931 | + OrgID: pgtype.Int8{Int64: orgID, Valid: true}, |
| 932 | + Name: item.Name, |
| 933 | + }) |
| 934 | + if err != nil { |
| 935 | + return err |
| 936 | + } |
| 937 | + plaintext, err := h.d.SecretBox.Open(row.Ciphertext, row.Nonce) |
| 899 | 938 | if err != nil { |
| 900 | 939 | return err |
| 901 | 940 | } |
@@ -912,6 +951,51 @@ func (h *Handlers) logMaskValues(ctx context.Context, repoID int64) ([]string, e |
| 912 | 951 | return secretMaskValues(resolved), nil |
| 913 | 952 | } |
| 914 | 953 | |
| 954 | +func (h *Handlers) storeJobSecretMaskSnapshot(ctx context.Context, db actionsdb.DBTX, jobID int64, values []string) error { |
| 955 | + if h.d.SecretBox == nil { |
| 956 | + return nil |
| 957 | + } |
| 958 | + if values == nil { |
| 959 | + values = []string{} |
| 960 | + } |
| 961 | + payload, err := json.Marshal(values) |
| 962 | + if err != nil { |
| 963 | + return err |
| 964 | + } |
| 965 | + ciphertext, nonce, err := h.d.SecretBox.Seal(payload) |
| 966 | + if err != nil { |
| 967 | + return err |
| 968 | + } |
| 969 | + return actionsdb.New().UpsertWorkflowJobSecretMask(ctx, db, actionsdb.UpsertWorkflowJobSecretMaskParams{ |
| 970 | + JobID: jobID, |
| 971 | + Ciphertext: ciphertext, |
| 972 | + Nonce: nonce, |
| 973 | + }) |
| 974 | +} |
| 975 | + |
| 976 | +func (h *Handlers) jobSecretMaskValues(ctx context.Context, jobID, repoID int64) ([]string, error) { |
| 977 | + if h.d.SecretBox == nil { |
| 978 | + return nil, nil |
| 979 | + } |
| 980 | + row, err := actionsdb.New().GetWorkflowJobSecretMask(ctx, h.d.Pool, jobID) |
| 981 | + if err != nil { |
| 982 | + if errors.Is(err, pgx.ErrNoRows) { |
| 983 | + return h.logMaskValues(ctx, repoID) |
| 984 | + } |
| 985 | + return nil, err |
| 986 | + } |
| 987 | + plaintext, err := h.d.SecretBox.Open(row.Ciphertext, row.Nonce) |
| 988 | + if err != nil { |
| 989 | + return nil, err |
| 990 | + } |
| 991 | + var values []string |
| 992 | + if err := json.Unmarshal(plaintext, &values); err != nil { |
| 993 | + return nil, err |
| 994 | + } |
| 995 | + sort.Strings(values) |
| 996 | + return values, nil |
| 997 | +} |
| 998 | + |
| 915 | 999 | func secretMaskValues(resolved map[string]string) []string { |
| 916 | 1000 | if len(resolved) == 0 { |
| 917 | 1001 | return nil |