Require OIDC bearer tokens on remote/virtual config writes when an issuer is set
This commit is contained in:
@@ -12,7 +12,7 @@ func cors(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.unkin.net/unkin/artifactapi/internal/testsupport"
|
||||
)
|
||||
|
||||
func TestServerOIDCWriteAuth(t *testing.T) {
|
||||
requireStack(t)
|
||||
iss := testsupport.NewOIDCIssuer()
|
||||
defer iss.Close()
|
||||
|
||||
cfg := *testSrv.cfg
|
||||
cfg.OIDCIssuer = iss.URL
|
||||
cfg.OIDCAudiences = []string{"artifactapi"}
|
||||
s := *testSrv
|
||||
s.cfg = &cfg
|
||||
ts := httptest.NewServer(s.routes())
|
||||
defer ts.Close()
|
||||
|
||||
good := iss.Token(nil, "artifactapi", "ci", time.Hour)
|
||||
do := func(method, path, body, token string) int {
|
||||
t.Helper()
|
||||
rq, _ := http.NewRequest(method, ts.URL+path, strings.NewReader(body))
|
||||
rq.Header.Set("Content-Type", "application/json")
|
||||
if token != "" {
|
||||
rq.Header.Set("Authorization", "Bearer "+token)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(rq)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, path, err)
|
||||
}
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
return resp.StatusCode
|
||||
}
|
||||
expect := func(want int, method, path, body, token string) {
|
||||
t.Helper()
|
||||
if got := do(method, path, body, token); got != want {
|
||||
t.Errorf("%s %s: status %d, want %d", method, path, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
remote := `{"name":"srv-oidc","package_type":"generic","repo_type":"local"}`
|
||||
virtual := `{"name":"srv-oidc-v","package_type":"generic","members":["srv-oidc"]}`
|
||||
|
||||
expect(401, "POST", "/api/v2/remotes", remote, "")
|
||||
expect(401, "POST", "/api/v2/remotes", remote, iss.Token(nil, "other", "ci", time.Hour))
|
||||
expect(201, "POST", "/api/v2/remotes", remote, good)
|
||||
expect(401, "PUT", "/api/v2/remotes/srv-oidc", `{"package_type":"generic"}`, "")
|
||||
expect(401, "DELETE", "/api/v2/remotes/srv-oidc", "", "")
|
||||
expect(401, "POST", "/api/v2/virtuals", virtual, "")
|
||||
expect(201, "POST", "/api/v2/virtuals", virtual, good)
|
||||
expect(401, "PUT", "/api/v2/virtuals/srv-oidc-v", virtual, "")
|
||||
expect(401, "DELETE", "/api/v2/virtuals/srv-oidc-v", "", "")
|
||||
|
||||
expect(200, "GET", "/api/v2/remotes", "", "")
|
||||
expect(200, "GET", "/api/v2/remotes/srv-oidc", "", "")
|
||||
expect(200, "GET", "/api/v2/virtuals/srv-oidc-v", "", "")
|
||||
expect(201, "PUT", "/api/v2/remotes/srv-oidc/files/a.bin", "payload", "")
|
||||
expect(200, "GET", "/api/v2/remotes/srv-oidc/files/a.bin", "", "")
|
||||
if got := do("DELETE", "/api/v2/remotes/srv-oidc/files/a.bin", "", ""); got >= 400 {
|
||||
t.Errorf("delete file: status %d", got)
|
||||
}
|
||||
if got := do("POST", "/api/v2/probe", `{}`, ""); got == 401 {
|
||||
t.Error("probe gated")
|
||||
}
|
||||
if got := do("GET", "/v2/", "", ""); got == 401 {
|
||||
t.Error("docker /v2 gated")
|
||||
}
|
||||
|
||||
expect(204, "DELETE", "/api/v2/virtuals/srv-oidc-v", "", good)
|
||||
expect(204, "DELETE", "/api/v2/remotes/srv-oidc", "", good)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
tfregistry "git.unkin.net/unkin/artifactapi/internal/api/terraform"
|
||||
v1 "git.unkin.net/unkin/artifactapi/internal/api/v1"
|
||||
v2 "git.unkin.net/unkin/artifactapi/internal/api/v2"
|
||||
"git.unkin.net/unkin/artifactapi/internal/auth"
|
||||
"git.unkin.net/unkin/artifactapi/internal/cache"
|
||||
"git.unkin.net/unkin/artifactapi/internal/config"
|
||||
"git.unkin.net/unkin/artifactapi/internal/database"
|
||||
@@ -186,10 +187,11 @@ func (s *Server) routes() chi.Router {
|
||||
statsHandler := v2.NewStatsHandler(s.db)
|
||||
eventsHandler := v2.NewEventsHandler()
|
||||
probeHandler := v2.NewProbeHandler(s.engine, s.db)
|
||||
writeAuth := auth.NewBearerOIDC(s.cfg.OIDCIssuer, s.cfg.OIDCAudiences)
|
||||
|
||||
r.Route("/api/v2", func(r chi.Router) {
|
||||
r.Mount("/remotes", remotesHandler.Routes())
|
||||
r.Mount("/virtuals", virtualsHandler.Routes())
|
||||
r.Mount("/remotes", writeAuth.RequireWrites(remotesHandler.Routes()))
|
||||
r.Mount("/virtuals", writeAuth.RequireWrites(virtualsHandler.Routes()))
|
||||
r.Mount("/health", healthHandler.Routes())
|
||||
r.Mount("/stats", statsHandler.Routes())
|
||||
r.Mount("/events", eventsHandler.Routes())
|
||||
|
||||
Reference in New Issue
Block a user