tangled
alpha
login
or
join now
evan.jarrett.net
/
at-container-registry
66
fork
atom
A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
atcr.io
docker
container
atproto
go
66
fork
atom
overview
issues
1
pulls
pipelines
rename example go files for documentation
evan.jarrett.net
4 months ago
ff7bc131
2d720e41
verified
This commit was signed with the committer's
known signature
.
evan.jarrett.net
SSH Key Fingerprint:
SHA256:bznk0uVPp7XFOl67P0uTM1pCjf2A4ojeP/lsUE7uauQ=
1/1
tests.yml
success
1m 13s
-439
2 changed files
expand all
collapse all
unified
split
examples
plugins
gatekeeper-provider
main.go
ratify-verifier
verifier.go
-225
examples/plugins/gatekeeper-provider/main.go
···
1
1
-
// Package main implements an OPA Gatekeeper External Data Provider for ATProto signature verification.
2
2
-
package main
3
3
-
4
4
-
import (
5
5
-
"context"
6
6
-
"encoding/json"
7
7
-
"fmt"
8
8
-
"log"
9
9
-
"net/http"
10
10
-
"os"
11
11
-
"time"
12
12
-
)
13
13
-
14
14
-
const (
15
15
-
// DefaultPort is the default HTTP port
16
16
-
DefaultPort = "8080"
17
17
-
18
18
-
// DefaultTrustPolicyPath is the default trust policy file path
19
19
-
DefaultTrustPolicyPath = "/config/trust-policy.yaml"
20
20
-
)
21
21
-
22
22
-
// Server is the HTTP server for the external data provider.
23
23
-
type Server struct {
24
24
-
verifier *Verifier
25
25
-
port string
26
26
-
httpServer *http.Server
27
27
-
}
28
28
-
29
29
-
// ProviderRequest is the request format from Gatekeeper.
30
30
-
type ProviderRequest struct {
31
31
-
Keys []string `json:"keys"`
32
32
-
Values []string `json:"values"`
33
33
-
}
34
34
-
35
35
-
// ProviderResponse is the response format to Gatekeeper.
36
36
-
type ProviderResponse struct {
37
37
-
SystemError string `json:"system_error,omitempty"`
38
38
-
Responses []map[string]interface{} `json:"responses"`
39
39
-
}
40
40
-
41
41
-
// VerificationResult holds the result of verifying a single image.
42
42
-
type VerificationResult struct {
43
43
-
Image string `json:"image"`
44
44
-
Verified bool `json:"verified"`
45
45
-
DID string `json:"did,omitempty"`
46
46
-
Handle string `json:"handle,omitempty"`
47
47
-
SignedAt time.Time `json:"signedAt,omitempty"`
48
48
-
CommitCID string `json:"commitCid,omitempty"`
49
49
-
Error string `json:"error,omitempty"`
50
50
-
}
51
51
-
52
52
-
// NewServer creates a new provider server.
53
53
-
func NewServer(verifier *Verifier, port string) *Server {
54
54
-
return &Server{
55
55
-
verifier: verifier,
56
56
-
port: port,
57
57
-
}
58
58
-
}
59
59
-
60
60
-
// Start starts the HTTP server.
61
61
-
func (s *Server) Start() error {
62
62
-
mux := http.NewServeMux()
63
63
-
64
64
-
// Provider endpoint (called by Gatekeeper)
65
65
-
mux.HandleFunc("/provide", s.handleProvide)
66
66
-
67
67
-
// Health check endpoints
68
68
-
mux.HandleFunc("/health", s.handleHealth)
69
69
-
mux.HandleFunc("/ready", s.handleReady)
70
70
-
71
71
-
// Metrics endpoint (Prometheus)
72
72
-
// TODO: Implement metrics
73
73
-
// mux.HandleFunc("/metrics", s.handleMetrics)
74
74
-
75
75
-
s.httpServer = &http.Server{
76
76
-
Addr: ":" + s.port,
77
77
-
Handler: mux,
78
78
-
ReadTimeout: 10 * time.Second,
79
79
-
WriteTimeout: 30 * time.Second,
80
80
-
IdleTimeout: 60 * time.Second,
81
81
-
}
82
82
-
83
83
-
log.Printf("Starting ATProto signature verification provider on port %s", s.port)
84
84
-
return s.httpServer.ListenAndServe()
85
85
-
}
86
86
-
87
87
-
// Stop gracefully stops the HTTP server.
88
88
-
func (s *Server) Stop(ctx context.Context) error {
89
89
-
if s.httpServer != nil {
90
90
-
return s.httpServer.Shutdown(ctx)
91
91
-
}
92
92
-
return nil
93
93
-
}
94
94
-
95
95
-
// handleProvide handles the provider endpoint called by Gatekeeper.
96
96
-
func (s *Server) handleProvide(w http.ResponseWriter, r *http.Request) {
97
97
-
if r.Method != http.MethodPost {
98
98
-
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
99
99
-
return
100
100
-
}
101
101
-
102
102
-
// Parse request
103
103
-
var req ProviderRequest
104
104
-
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
105
105
-
log.Printf("ERROR: failed to parse request: %v", err)
106
106
-
http.Error(w, fmt.Sprintf("invalid request: %v", err), http.StatusBadRequest)
107
107
-
return
108
108
-
}
109
109
-
110
110
-
log.Printf("INFO: received verification request for %d images", len(req.Values))
111
111
-
112
112
-
// Verify each image
113
113
-
responses := make([]map[string]interface{}, 0, len(req.Values))
114
114
-
for _, image := range req.Values {
115
115
-
result := s.verifyImage(r.Context(), image)
116
116
-
responses = append(responses, structToMap(result))
117
117
-
}
118
118
-
119
119
-
// Send response
120
120
-
resp := ProviderResponse{
121
121
-
Responses: responses,
122
122
-
}
123
123
-
124
124
-
w.Header().Set("Content-Type", "application/json")
125
125
-
if err := json.NewEncoder(w).Encode(resp); err != nil {
126
126
-
log.Printf("ERROR: failed to encode response: %v", err)
127
127
-
}
128
128
-
}
129
129
-
130
130
-
// verifyImage verifies a single image.
131
131
-
func (s *Server) verifyImage(ctx context.Context, image string) VerificationResult {
132
132
-
start := time.Now()
133
133
-
log.Printf("INFO: verifying image: %s", image)
134
134
-
135
135
-
// Call verifier
136
136
-
verified, metadata, err := s.verifier.Verify(ctx, image)
137
137
-
duration := time.Since(start)
138
138
-
139
139
-
if err != nil {
140
140
-
log.Printf("ERROR: verification failed for %s: %v (duration: %v)", image, err, duration)
141
141
-
return VerificationResult{
142
142
-
Image: image,
143
143
-
Verified: false,
144
144
-
Error: err.Error(),
145
145
-
}
146
146
-
}
147
147
-
148
148
-
if !verified {
149
149
-
log.Printf("WARN: image %s failed verification (duration: %v)", image, duration)
150
150
-
return VerificationResult{
151
151
-
Image: image,
152
152
-
Verified: false,
153
153
-
Error: "signature verification failed",
154
154
-
}
155
155
-
}
156
156
-
157
157
-
log.Printf("INFO: image %s verified successfully (DID: %s, duration: %v)",
158
158
-
image, metadata.DID, duration)
159
159
-
160
160
-
return VerificationResult{
161
161
-
Image: image,
162
162
-
Verified: true,
163
163
-
DID: metadata.DID,
164
164
-
Handle: metadata.Handle,
165
165
-
SignedAt: metadata.SignedAt,
166
166
-
CommitCID: metadata.CommitCID,
167
167
-
}
168
168
-
}
169
169
-
170
170
-
// handleHealth handles health check requests.
171
171
-
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
172
172
-
w.Header().Set("Content-Type", "application/json")
173
173
-
json.NewEncoder(w).Encode(map[string]string{
174
174
-
"status": "ok",
175
175
-
"version": "1.0.0",
176
176
-
})
177
177
-
}
178
178
-
179
179
-
// handleReady handles readiness check requests.
180
180
-
func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
181
181
-
// TODO: Check dependencies (DID resolver, PDS connectivity)
182
182
-
w.Header().Set("Content-Type", "application/json")
183
183
-
json.NewEncoder(w).Encode(map[string]string{
184
184
-
"status": "ready",
185
185
-
})
186
186
-
}
187
187
-
188
188
-
// structToMap converts a struct to a map for JSON encoding.
189
189
-
func structToMap(v interface{}) map[string]interface{} {
190
190
-
data, _ := json.Marshal(v)
191
191
-
var m map[string]interface{}
192
192
-
json.Unmarshal(data, &m)
193
193
-
return m
194
194
-
}
195
195
-
196
196
-
func main() {
197
197
-
// Load configuration
198
198
-
port := os.Getenv("HTTP_PORT")
199
199
-
if port == "" {
200
200
-
port = DefaultPort
201
201
-
}
202
202
-
203
203
-
trustPolicyPath := os.Getenv("TRUST_POLICY_PATH")
204
204
-
if trustPolicyPath == "" {
205
205
-
trustPolicyPath = DefaultTrustPolicyPath
206
206
-
}
207
207
-
208
208
-
// Create verifier
209
209
-
verifier, err := NewVerifier(trustPolicyPath)
210
210
-
if err != nil {
211
211
-
log.Fatalf("FATAL: failed to create verifier: %v", err)
212
212
-
}
213
213
-
214
214
-
// Create server
215
215
-
server := NewServer(verifier, port)
216
216
-
217
217
-
// Start server
218
218
-
if err := server.Start(); err != nil && err != http.ErrServerClosed {
219
219
-
log.Fatalf("FATAL: server error: %v", err)
220
220
-
}
221
221
-
}
222
222
-
223
223
-
// TODO: Implement verifier.go with ATProto signature verification logic
224
224
-
// TODO: Implement resolver.go with DID resolution
225
225
-
// TODO: Implement crypto.go with K-256 signature verification
-214
examples/plugins/ratify-verifier/verifier.go
···
1
1
-
// Package atproto implements a Ratify verifier plugin for ATProto signatures.
2
2
-
package atproto
3
3
-
4
4
-
import (
5
5
-
"context"
6
6
-
"encoding/json"
7
7
-
"fmt"
8
8
-
"time"
9
9
-
10
10
-
"github.com/ratify-project/ratify/pkg/common"
11
11
-
"github.com/ratify-project/ratify/pkg/ocispecs"
12
12
-
"github.com/ratify-project/ratify/pkg/referrerstore"
13
13
-
"github.com/ratify-project/ratify/pkg/verifier"
14
14
-
)
15
15
-
16
16
-
const (
17
17
-
// VerifierName is the name of this verifier
18
18
-
VerifierName = "atproto"
19
19
-
20
20
-
// VerifierType is the type of this verifier
21
21
-
VerifierType = "atproto"
22
22
-
23
23
-
// ATProtoSignatureArtifactType is the OCI artifact type for ATProto signatures
24
24
-
ATProtoSignatureArtifactType = "application/vnd.atproto.signature.v1+json"
25
25
-
)
26
26
-
27
27
-
// ATProtoVerifier implements the Ratify ReferenceVerifier interface for ATProto signatures.
28
28
-
type ATProtoVerifier struct {
29
29
-
name string
30
30
-
config ATProtoConfig
31
31
-
resolver *Resolver
32
32
-
verifier *SignatureVerifier
33
33
-
trustStore *TrustStore
34
34
-
}
35
35
-
36
36
-
// ATProtoConfig holds configuration for the ATProto verifier.
37
37
-
type ATProtoConfig struct {
38
38
-
// TrustPolicyPath is the path to the trust policy YAML file
39
39
-
TrustPolicyPath string `json:"trustPolicyPath"`
40
40
-
41
41
-
// DIDResolverTimeout is the timeout for DID resolution
42
42
-
DIDResolverTimeout time.Duration `json:"didResolverTimeout"`
43
43
-
44
44
-
// PDSTimeout is the timeout for PDS XRPC calls
45
45
-
PDSTimeout time.Duration `json:"pdsTimeout"`
46
46
-
47
47
-
// CacheEnabled enables caching of DID documents and public keys
48
48
-
CacheEnabled bool `json:"cacheEnabled"`
49
49
-
50
50
-
// CacheTTL is the cache TTL for DID documents and public keys
51
51
-
CacheTTL time.Duration `json:"cacheTTL"`
52
52
-
}
53
53
-
54
54
-
// ATProtoSignature represents the ATProto signature metadata stored in the OCI artifact.
55
55
-
type ATProtoSignature struct {
56
56
-
Type string `json:"$type"`
57
57
-
Version string `json:"version"`
58
58
-
Subject struct {
59
59
-
Digest string `json:"digest"`
60
60
-
MediaType string `json:"mediaType"`
61
61
-
} `json:"subject"`
62
62
-
ATProto struct {
63
63
-
DID string `json:"did"`
64
64
-
Handle string `json:"handle"`
65
65
-
PDSEndpoint string `json:"pdsEndpoint"`
66
66
-
RecordURI string `json:"recordUri"`
67
67
-
CommitCID string `json:"commitCid"`
68
68
-
SignedAt time.Time `json:"signedAt"`
69
69
-
} `json:"atproto"`
70
70
-
Signature struct {
71
71
-
Algorithm string `json:"algorithm"`
72
72
-
KeyID string `json:"keyId"`
73
73
-
PublicKeyMultibase string `json:"publicKeyMultibase"`
74
74
-
} `json:"signature"`
75
75
-
}
76
76
-
77
77
-
// NewATProtoVerifier creates a new ATProto verifier instance.
78
78
-
func NewATProtoVerifier(name string, config ATProtoConfig) (*ATProtoVerifier, error) {
79
79
-
// Load trust policy
80
80
-
trustStore, err := LoadTrustStore(config.TrustPolicyPath)
81
81
-
if err != nil {
82
82
-
return nil, fmt.Errorf("failed to load trust policy: %w", err)
83
83
-
}
84
84
-
85
85
-
// Create resolver with caching
86
86
-
resolver := NewResolver(config.DIDResolverTimeout, config.CacheEnabled, config.CacheTTL)
87
87
-
88
88
-
// Create signature verifier
89
89
-
verifier := NewSignatureVerifier(config.PDSTimeout)
90
90
-
91
91
-
return &ATProtoVerifier{
92
92
-
name: name,
93
93
-
config: config,
94
94
-
resolver: resolver,
95
95
-
verifier: verifier,
96
96
-
trustStore: trustStore,
97
97
-
}, nil
98
98
-
}
99
99
-
100
100
-
// Name returns the name of this verifier.
101
101
-
func (v *ATProtoVerifier) Name() string {
102
102
-
return v.name
103
103
-
}
104
104
-
105
105
-
// Type returns the type of this verifier.
106
106
-
func (v *ATProtoVerifier) Type() string {
107
107
-
return VerifierType
108
108
-
}
109
109
-
110
110
-
// CanVerify returns true if this verifier can verify the given artifact type.
111
111
-
func (v *ATProtoVerifier) CanVerify(artifactType string) bool {
112
112
-
return artifactType == ATProtoSignatureArtifactType
113
113
-
}
114
114
-
115
115
-
// VerifyReference verifies an ATProto signature artifact.
116
116
-
func (v *ATProtoVerifier) VerifyReference(
117
117
-
ctx context.Context,
118
118
-
subjectRef common.Reference,
119
119
-
referenceDesc ocispecs.ReferenceDescriptor,
120
120
-
store referrerstore.ReferrerStore,
121
121
-
) (verifier.VerifierResult, error) {
122
122
-
// 1. Fetch signature blob from store
123
123
-
sigBlob, err := store.GetBlobContent(ctx, subjectRef, referenceDesc.Digest)
124
124
-
if err != nil {
125
125
-
return v.failureResult(fmt.Sprintf("failed to fetch signature blob: %v", err)), err
126
126
-
}
127
127
-
128
128
-
// 2. Parse ATProto signature metadata
129
129
-
var sigData ATProtoSignature
130
130
-
if err := json.Unmarshal(sigBlob, &sigData); err != nil {
131
131
-
return v.failureResult(fmt.Sprintf("failed to parse signature metadata: %v", err)), err
132
132
-
}
133
133
-
134
134
-
// Validate signature format
135
135
-
if err := v.validateSignature(&sigData); err != nil {
136
136
-
return v.failureResult(fmt.Sprintf("invalid signature format: %v", err)), err
137
137
-
}
138
138
-
139
139
-
// 3. Check trust policy first (fail fast if DID not trusted)
140
140
-
if !v.trustStore.IsTrusted(sigData.ATProto.DID, time.Now()) {
141
141
-
return v.failureResult(fmt.Sprintf("DID %s not in trusted list", sigData.ATProto.DID)),
142
142
-
fmt.Errorf("untrusted DID")
143
143
-
}
144
144
-
145
145
-
// 4. Resolve DID to public key
146
146
-
pubKey, err := v.resolver.ResolveDIDToPublicKey(ctx, sigData.ATProto.DID)
147
147
-
if err != nil {
148
148
-
return v.failureResult(fmt.Sprintf("failed to resolve DID: %v", err)), err
149
149
-
}
150
150
-
151
151
-
// 5. Fetch repository commit from PDS
152
152
-
commit, err := v.verifier.FetchCommit(ctx, sigData.ATProto.PDSEndpoint,
153
153
-
sigData.ATProto.DID, sigData.ATProto.CommitCID)
154
154
-
if err != nil {
155
155
-
return v.failureResult(fmt.Sprintf("failed to fetch commit: %v", err)), err
156
156
-
}
157
157
-
158
158
-
// 6. Verify K-256 signature
159
159
-
if err := v.verifier.VerifySignature(pubKey, commit); err != nil {
160
160
-
return v.failureResult(fmt.Sprintf("signature verification failed: %v", err)), err
161
161
-
}
162
162
-
163
163
-
// 7. Success - return detailed result
164
164
-
return verifier.VerifierResult{
165
165
-
IsSuccess: true,
166
166
-
Name: v.name,
167
167
-
Type: v.Type(),
168
168
-
Message: fmt.Sprintf("Successfully verified ATProto signature for DID %s", sigData.ATProto.DID),
169
169
-
Extensions: map[string]interface{}{
170
170
-
"did": sigData.ATProto.DID,
171
171
-
"handle": sigData.ATProto.Handle,
172
172
-
"signedAt": sigData.ATProto.SignedAt,
173
173
-
"commitCid": sigData.ATProto.CommitCID,
174
174
-
"pdsEndpoint": sigData.ATProto.PDSEndpoint,
175
175
-
},
176
176
-
}, nil
177
177
-
}
178
178
-
179
179
-
// validateSignature validates the signature metadata format.
180
180
-
func (v *ATProtoVerifier) validateSignature(sig *ATProtoSignature) error {
181
181
-
if sig.Type != "io.atcr.atproto.signature" {
182
182
-
return fmt.Errorf("invalid signature type: %s", sig.Type)
183
183
-
}
184
184
-
if sig.ATProto.DID == "" {
185
185
-
return fmt.Errorf("missing DID")
186
186
-
}
187
187
-
if sig.ATProto.PDSEndpoint == "" {
188
188
-
return fmt.Errorf("missing PDS endpoint")
189
189
-
}
190
190
-
if sig.ATProto.CommitCID == "" {
191
191
-
return fmt.Errorf("missing commit CID")
192
192
-
}
193
193
-
if sig.Signature.Algorithm != "ECDSA-K256-SHA256" {
194
194
-
return fmt.Errorf("unsupported signature algorithm: %s", sig.Signature.Algorithm)
195
195
-
}
196
196
-
return nil
197
197
-
}
198
198
-
199
199
-
// failureResult creates a failure result with the given message.
200
200
-
func (v *ATProtoVerifier) failureResult(message string) verifier.VerifierResult {
201
201
-
return verifier.VerifierResult{
202
202
-
IsSuccess: false,
203
203
-
Name: v.name,
204
204
-
Type: v.Type(),
205
205
-
Message: message,
206
206
-
Extensions: map[string]interface{}{
207
207
-
"error": message,
208
208
-
},
209
209
-
}
210
210
-
}
211
211
-
212
212
-
// TODO: Implement resolver.go with DID resolution logic
213
213
-
// TODO: Implement crypto.go with K-256 signature verification
214
214
-
// TODO: Implement config.go with trust policy loading