detached 72f8f2e

feat(ci): OIDC ID tokens for CI jobs (#14)

commit
72f8f2e940f120e52aae70f75492365ad1c30225
author
brice · · 2026-10-16 07:40 UTC
committer
bnema · · 2026-10-16 07:40 UTC
parent
faa62b9
signature
SSH UnverifiedSHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivA The key is unknown
refs
tag: v0.4.0
7 files · +298 -20
M README.md +1 -1
@@ -29,7 +29,7 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
2929 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |
3030 | `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`; artifacts v4 for CI runners, scoped to the run of the job token |
3131 | `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact`; artifacts of a run for people |
32-| `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |
32+| `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key), `RotateOIDCKey` (key of the CI OIDC ID tokens, owner only) |
3333
3434 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
3535
@@ -29,7 +29,7 @@ Handlers are mounted under `/-/api/` by the server. Errors are Connect codes.
29 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |29 | `EnvironmentService` | `CreateEnvironment`, `ListEnvironments`, `DeleteEnvironment` |
30 | `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`; artifacts v4 for CI runners, scoped to the run of the job token |30 | `JobArtifactService` | `CreateJobArtifact`, `UploadJobArtifactBlock` (client stream), `CommitJobArtifact`, `FinalizeJobArtifact`, `ListJobArtifacts`, `DownloadJobArtifact` (streamed, resumable), `DeleteJobArtifact`; artifacts v4 for CI runners, scoped to the run of the job token |
31 | `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact`; artifacts of a run for people |31 | `ArtifactService` | `ListArtifacts`, `DownloadArtifact` (streamed, resumable), `DeleteArtifact`; artifacts of a run for people |
32-| `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key) |32+| `ServerService` | `GetVersion` (server version and feature list), `GetSigningKey` (commit signing key), `RotateOIDCKey` (key of the CI OIDC ID tokens, owner only) |
33 33
34 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.34 Tokens carry scopes only; presets such as `cli` or `agent` are a client concern.
35 35
M gen/ermite/ermiteconnect/server.connect.go +49 -0
@@ -41,6 +41,9 @@ const (
4141 // ServerServiceGetSigningKeyProcedure is the fully-qualified name of the ServerService's
4242 // GetSigningKey RPC.
4343 ServerServiceGetSigningKeyProcedure = "/ermite.ServerService/GetSigningKey"
44+ // ServerServiceRotateOIDCKeyProcedure is the fully-qualified name of the ServerService's
45+ // RotateOIDCKey RPC.
46+ ServerServiceRotateOIDCKeyProcedure = "/ermite.ServerService/RotateOIDCKey"
4447 )
4548
4649 // ServerServiceClient is a client for the ermite.ServerService service.
@@ -55,6 +58,17 @@ type ServerServiceClient interface {
5558 // Servers that implement it list "signing_key" in
5659 // GetVersionResponse.features.
5760 GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
61+ // RotateOIDCKey replaces the key with which the server signs the OIDC ID
62+ // tokens of CI jobs (Job.id_token_url) by a new one. The previous key stops
63+ // signing at once but stays in the server's JWKS
64+ // ("/-/oidc/.well-known/jwks") for 24 hours, so that tokens it signed still
65+ // verify until they expire. It needs owner rights: the admin token, the
66+ // owner's session, or a token with every scope and no repository list or
67+ // branch prefixes (permission_denied otherwise). It fails with
68+ // failed_precondition when the server issues no ID tokens (it has no
69+ // secrets key to seal the key with). Servers that implement it list
70+ // "ci.oidc" in GetVersionResponse.features.
71+ RotateOIDCKey(context.Context, *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error)
5872 }
5973
6074 // NewServerServiceClient constructs a client for the ermite.ServerService service. By default, it
@@ -80,6 +94,12 @@ func NewServerServiceClient(httpClient connect.HTTPClient, baseURL string, opts
8094 connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
8195 connect.WithClientOptions(opts...),
8296 ),
97+ rotateOIDCKey: connect.NewClient[ermite.RotateOIDCKeyRequest, ermite.RotateOIDCKeyResponse](
98+ httpClient,
99+ baseURL+ServerServiceRotateOIDCKeyProcedure,
100+ connect.WithSchema(serverServiceMethods.ByName("RotateOIDCKey")),
101+ connect.WithClientOptions(opts...),
102+ ),
83103 }
84104 }
85105
@@ -87,6 +107,7 @@ func NewServerServiceClient(httpClient connect.HTTPClient, baseURL string, opts
87107 type serverServiceClient struct {
88108 getVersion *connect.Client[ermite.GetVersionRequest, ermite.GetVersionResponse]
89109 getSigningKey *connect.Client[ermite.GetSigningKeyRequest, ermite.GetSigningKeyResponse]
110+ rotateOIDCKey *connect.Client[ermite.RotateOIDCKeyRequest, ermite.RotateOIDCKeyResponse]
90111 }
91112
92113 // GetVersion calls ermite.ServerService.GetVersion.
@@ -99,6 +120,11 @@ func (c *serverServiceClient) GetSigningKey(ctx context.Context, req *connect.Re
99120 return c.getSigningKey.CallUnary(ctx, req)
100121 }
101122
123+// RotateOIDCKey calls ermite.ServerService.RotateOIDCKey.
124+func (c *serverServiceClient) RotateOIDCKey(ctx context.Context, req *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error) {
125+ return c.rotateOIDCKey.CallUnary(ctx, req)
126+}
127+
102128 // ServerServiceHandler is an implementation of the ermite.ServerService service.
103129 type ServerServiceHandler interface {
104130 // GetVersion returns the server version and the optional capabilities of the
@@ -111,6 +137,17 @@ type ServerServiceHandler interface {
111137 // Servers that implement it list "signing_key" in
112138 // GetVersionResponse.features.
113139 GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
140+ // RotateOIDCKey replaces the key with which the server signs the OIDC ID
141+ // tokens of CI jobs (Job.id_token_url) by a new one. The previous key stops
142+ // signing at once but stays in the server's JWKS
143+ // ("/-/oidc/.well-known/jwks") for 24 hours, so that tokens it signed still
144+ // verify until they expire. It needs owner rights: the admin token, the
145+ // owner's session, or a token with every scope and no repository list or
146+ // branch prefixes (permission_denied otherwise). It fails with
147+ // failed_precondition when the server issues no ID tokens (it has no
148+ // secrets key to seal the key with). Servers that implement it list
149+ // "ci.oidc" in GetVersionResponse.features.
150+ RotateOIDCKey(context.Context, *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error)
114151 }
115152
116153 // NewServerServiceHandler builds an HTTP handler from the service implementation. It returns the
@@ -132,12 +169,20 @@ func NewServerServiceHandler(svc ServerServiceHandler, opts ...connect.HandlerOp
132169 connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
133170 connect.WithHandlerOptions(opts...),
134171 )
172+ serverServiceRotateOIDCKeyHandler := connect.NewUnaryHandler(
173+ ServerServiceRotateOIDCKeyProcedure,
174+ svc.RotateOIDCKey,
175+ connect.WithSchema(serverServiceMethods.ByName("RotateOIDCKey")),
176+ connect.WithHandlerOptions(opts...),
177+ )
135178 return "/ermite.ServerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136179 switch r.URL.Path {
137180 case ServerServiceGetVersionProcedure:
138181 serverServiceGetVersionHandler.ServeHTTP(w, r)
139182 case ServerServiceGetSigningKeyProcedure:
140183 serverServiceGetSigningKeyHandler.ServeHTTP(w, r)
184+ case ServerServiceRotateOIDCKeyProcedure:
185+ serverServiceRotateOIDCKeyHandler.ServeHTTP(w, r)
141186 default:
142187 http.NotFound(w, r)
143188 }
@@ -154,3 +199,7 @@ func (UnimplementedServerServiceHandler) GetVersion(context.Context, *connect.Re
154199 func (UnimplementedServerServiceHandler) GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error) {
155200 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetSigningKey is not implemented"))
156201 }
202+
203+func (UnimplementedServerServiceHandler) RotateOIDCKey(context.Context, *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error) {
204+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.RotateOIDCKey is not implemented"))
205+}
@@ -41,6 +41,9 @@ const (
41 // ServerServiceGetSigningKeyProcedure is the fully-qualified name of the ServerService's41 // ServerServiceGetSigningKeyProcedure is the fully-qualified name of the ServerService's
42 // GetSigningKey RPC.42 // GetSigningKey RPC.
43 ServerServiceGetSigningKeyProcedure = "/ermite.ServerService/GetSigningKey"43 ServerServiceGetSigningKeyProcedure = "/ermite.ServerService/GetSigningKey"
44+ // ServerServiceRotateOIDCKeyProcedure is the fully-qualified name of the ServerService's
45+ // RotateOIDCKey RPC.
46+ ServerServiceRotateOIDCKeyProcedure = "/ermite.ServerService/RotateOIDCKey"
44 )47 )
45 48
46 // ServerServiceClient is a client for the ermite.ServerService service.49 // ServerServiceClient is a client for the ermite.ServerService service.
@@ -55,6 +58,17 @@ type ServerServiceClient interface {
55 // Servers that implement it list "signing_key" in58 // Servers that implement it list "signing_key" in
56 // GetVersionResponse.features.59 // GetVersionResponse.features.
57 GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)60 GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
61+ // RotateOIDCKey replaces the key with which the server signs the OIDC ID
62+ // tokens of CI jobs (Job.id_token_url) by a new one. The previous key stops
63+ // signing at once but stays in the server's JWKS
64+ // ("/-/oidc/.well-known/jwks") for 24 hours, so that tokens it signed still
65+ // verify until they expire. It needs owner rights: the admin token, the
66+ // owner's session, or a token with every scope and no repository list or
67+ // branch prefixes (permission_denied otherwise). It fails with
68+ // failed_precondition when the server issues no ID tokens (it has no
69+ // secrets key to seal the key with). Servers that implement it list
70+ // "ci.oidc" in GetVersionResponse.features.
71+ RotateOIDCKey(context.Context, *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error)
58 }72 }
59 73
60 // NewServerServiceClient constructs a client for the ermite.ServerService service. By default, it74 // NewServerServiceClient constructs a client for the ermite.ServerService service. By default, it
@@ -80,6 +94,12 @@ func NewServerServiceClient(httpClient connect.HTTPClient, baseURL string, opts
80 connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),94 connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
81 connect.WithClientOptions(opts...),95 connect.WithClientOptions(opts...),
82 ),96 ),
97+ rotateOIDCKey: connect.NewClient[ermite.RotateOIDCKeyRequest, ermite.RotateOIDCKeyResponse](
98+ httpClient,
99+ baseURL+ServerServiceRotateOIDCKeyProcedure,
100+ connect.WithSchema(serverServiceMethods.ByName("RotateOIDCKey")),
101+ connect.WithClientOptions(opts...),
102+ ),
83 }103 }
84 }104 }
85 105
@@ -87,6 +107,7 @@ func NewServerServiceClient(httpClient connect.HTTPClient, baseURL string, opts
87 type serverServiceClient struct {107 type serverServiceClient struct {
88 getVersion *connect.Client[ermite.GetVersionRequest, ermite.GetVersionResponse]108 getVersion *connect.Client[ermite.GetVersionRequest, ermite.GetVersionResponse]
89 getSigningKey *connect.Client[ermite.GetSigningKeyRequest, ermite.GetSigningKeyResponse]109 getSigningKey *connect.Client[ermite.GetSigningKeyRequest, ermite.GetSigningKeyResponse]
110+ rotateOIDCKey *connect.Client[ermite.RotateOIDCKeyRequest, ermite.RotateOIDCKeyResponse]
90 }111 }
91 112
92 // GetVersion calls ermite.ServerService.GetVersion.113 // GetVersion calls ermite.ServerService.GetVersion.
@@ -99,6 +120,11 @@ func (c *serverServiceClient) GetSigningKey(ctx context.Context, req *connect.Re
99 return c.getSigningKey.CallUnary(ctx, req)120 return c.getSigningKey.CallUnary(ctx, req)
100 }121 }
101 122
123+// RotateOIDCKey calls ermite.ServerService.RotateOIDCKey.
124+func (c *serverServiceClient) RotateOIDCKey(ctx context.Context, req *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error) {
125+ return c.rotateOIDCKey.CallUnary(ctx, req)
126+}
127+
102 // ServerServiceHandler is an implementation of the ermite.ServerService service.128 // ServerServiceHandler is an implementation of the ermite.ServerService service.
103 type ServerServiceHandler interface {129 type ServerServiceHandler interface {
104 // GetVersion returns the server version and the optional capabilities of the130 // GetVersion returns the server version and the optional capabilities of the
@@ -111,6 +137,17 @@ type ServerServiceHandler interface {
111 // Servers that implement it list "signing_key" in137 // Servers that implement it list "signing_key" in
112 // GetVersionResponse.features.138 // GetVersionResponse.features.
113 GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)139 GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error)
140+ // RotateOIDCKey replaces the key with which the server signs the OIDC ID
141+ // tokens of CI jobs (Job.id_token_url) by a new one. The previous key stops
142+ // signing at once but stays in the server's JWKS
143+ // ("/-/oidc/.well-known/jwks") for 24 hours, so that tokens it signed still
144+ // verify until they expire. It needs owner rights: the admin token, the
145+ // owner's session, or a token with every scope and no repository list or
146+ // branch prefixes (permission_denied otherwise). It fails with
147+ // failed_precondition when the server issues no ID tokens (it has no
148+ // secrets key to seal the key with). Servers that implement it list
149+ // "ci.oidc" in GetVersionResponse.features.
150+ RotateOIDCKey(context.Context, *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error)
114 }151 }
115 152
116 // NewServerServiceHandler builds an HTTP handler from the service implementation. It returns the153 // NewServerServiceHandler builds an HTTP handler from the service implementation. It returns the
@@ -132,12 +169,20 @@ func NewServerServiceHandler(svc ServerServiceHandler, opts ...connect.HandlerOp
132 connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),169 connect.WithSchema(serverServiceMethods.ByName("GetSigningKey")),
133 connect.WithHandlerOptions(opts...),170 connect.WithHandlerOptions(opts...),
134 )171 )
172+ serverServiceRotateOIDCKeyHandler := connect.NewUnaryHandler(
173+ ServerServiceRotateOIDCKeyProcedure,
174+ svc.RotateOIDCKey,
175+ connect.WithSchema(serverServiceMethods.ByName("RotateOIDCKey")),
176+ connect.WithHandlerOptions(opts...),
177+ )
135 return "/ermite.ServerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {178 return "/ermite.ServerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136 switch r.URL.Path {179 switch r.URL.Path {
137 case ServerServiceGetVersionProcedure:180 case ServerServiceGetVersionProcedure:
138 serverServiceGetVersionHandler.ServeHTTP(w, r)181 serverServiceGetVersionHandler.ServeHTTP(w, r)
139 case ServerServiceGetSigningKeyProcedure:182 case ServerServiceGetSigningKeyProcedure:
140 serverServiceGetSigningKeyHandler.ServeHTTP(w, r)183 serverServiceGetSigningKeyHandler.ServeHTTP(w, r)
184+ case ServerServiceRotateOIDCKeyProcedure:
185+ serverServiceRotateOIDCKeyHandler.ServeHTTP(w, r)
141 default:186 default:
142 http.NotFound(w, r)187 http.NotFound(w, r)
143 }188 }
@@ -154,3 +199,7 @@ func (UnimplementedServerServiceHandler) GetVersion(context.Context, *connect.Re
154 func (UnimplementedServerServiceHandler) GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error) {199 func (UnimplementedServerServiceHandler) GetSigningKey(context.Context, *connect.Request[ermite.GetSigningKeyRequest]) (*connect.Response[ermite.GetSigningKeyResponse], error) {
155 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetSigningKey is not implemented"))200 return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.GetSigningKey is not implemented"))
156 }201 }
202+
203+func (UnimplementedServerServiceHandler) RotateOIDCKey(context.Context, *connect.Request[ermite.RotateOIDCKeyRequest]) (*connect.Response[ermite.RotateOIDCKeyResponse], error) {
204+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("ermite.ServerService.RotateOIDCKey is not implemented"))
205+}
M gen/ermite/runner.pb.go +46 -5 generated file: collapsed
M gen/ermite/server.pb.go +144 -14
@@ -11,6 +11,7 @@ package ermite
1111 import (
1212 protoreflect "google.golang.org/protobuf/reflect/protoreflect"
1313 protoimpl "google.golang.org/protobuf/runtime/protoimpl"
14+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
1415 reflect "reflect"
1516 sync "sync"
1617 unsafe "unsafe"
@@ -260,11 +261,126 @@ func (x *GetSigningKeyResponse) GetAllowedSigners() string {
260261 return ""
261262 }
262263
264+// RotateOIDCKeyRequest is empty.
265+type RotateOIDCKeyRequest struct {
266+ state protoimpl.MessageState `protogen:"open.v1"`
267+ unknownFields protoimpl.UnknownFields
268+ sizeCache protoimpl.SizeCache
269+}
270+
271+func (x *RotateOIDCKeyRequest) Reset() {
272+ *x = RotateOIDCKeyRequest{}
273+ mi := &file_ermite_server_proto_msgTypes[4]
274+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
275+ ms.StoreMessageInfo(mi)
276+}
277+
278+func (x *RotateOIDCKeyRequest) String() string {
279+ return protoimpl.X.MessageStringOf(x)
280+}
281+
282+func (*RotateOIDCKeyRequest) ProtoMessage() {}
283+
284+func (x *RotateOIDCKeyRequest) ProtoReflect() protoreflect.Message {
285+ mi := &file_ermite_server_proto_msgTypes[4]
286+ if x != nil {
287+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
288+ if ms.LoadMessageInfo() == nil {
289+ ms.StoreMessageInfo(mi)
290+ }
291+ return ms
292+ }
293+ return mi.MessageOf(x)
294+}
295+
296+// Deprecated: Use RotateOIDCKeyRequest.ProtoReflect.Descriptor instead.
297+func (*RotateOIDCKeyRequest) Descriptor() ([]byte, []int) {
298+ return file_ermite_server_proto_rawDescGZIP(), []int{4}
299+}
300+
301+// RotateOIDCKeyResponse describes the new signing key of the ID tokens.
302+type RotateOIDCKeyResponse struct {
303+ state protoimpl.MessageState `protogen:"open.v1"`
304+ // Issuer is the "iss" of the ID tokens, for example
305+ // "https://git.example.com/-/oidc". Its discovery document is at
306+ // "<issuer>/.well-known/openid-configuration".
307+ Issuer string `protobuf:"bytes,1,opt,name=issuer,proto3" json:"issuer,omitempty"`
308+ // Kid is the key id of the new key, the "kid" of the tokens it signs: the
309+ // RFC 7638 thumbprint of its public key, base64url without padding.
310+ Kid string `protobuf:"bytes,2,opt,name=kid,proto3" json:"kid,omitempty"`
311+ // RetiredKid is the key id of the key it replaces; empty when there was
312+ // none.
313+ RetiredKid string `protobuf:"bytes,3,opt,name=retired_kid,json=retiredKid,proto3" json:"retired_kid,omitempty"`
314+ // RetiredUntil is when the replaced key leaves the JWKS. Unset when there
315+ // was none.
316+ RetiredUntil *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=retired_until,json=retiredUntil,proto3" json:"retired_until,omitempty"`
317+ unknownFields protoimpl.UnknownFields
318+ sizeCache protoimpl.SizeCache
319+}
320+
321+func (x *RotateOIDCKeyResponse) Reset() {
322+ *x = RotateOIDCKeyResponse{}
323+ mi := &file_ermite_server_proto_msgTypes[5]
324+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
325+ ms.StoreMessageInfo(mi)
326+}
327+
328+func (x *RotateOIDCKeyResponse) String() string {
329+ return protoimpl.X.MessageStringOf(x)
330+}
331+
332+func (*RotateOIDCKeyResponse) ProtoMessage() {}
333+
334+func (x *RotateOIDCKeyResponse) ProtoReflect() protoreflect.Message {
335+ mi := &file_ermite_server_proto_msgTypes[5]
336+ if x != nil {
337+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
338+ if ms.LoadMessageInfo() == nil {
339+ ms.StoreMessageInfo(mi)
340+ }
341+ return ms
342+ }
343+ return mi.MessageOf(x)
344+}
345+
346+// Deprecated: Use RotateOIDCKeyResponse.ProtoReflect.Descriptor instead.
347+func (*RotateOIDCKeyResponse) Descriptor() ([]byte, []int) {
348+ return file_ermite_server_proto_rawDescGZIP(), []int{5}
349+}
350+
351+func (x *RotateOIDCKeyResponse) GetIssuer() string {
352+ if x != nil {
353+ return x.Issuer
354+ }
355+ return ""
356+}
357+
358+func (x *RotateOIDCKeyResponse) GetKid() string {
359+ if x != nil {
360+ return x.Kid
361+ }
362+ return ""
363+}
364+
365+func (x *RotateOIDCKeyResponse) GetRetiredKid() string {
366+ if x != nil {
367+ return x.RetiredKid
368+ }
369+ return ""
370+}
371+
372+func (x *RotateOIDCKeyResponse) GetRetiredUntil() *timestamppb.Timestamp {
373+ if x != nil {
374+ return x.RetiredUntil
375+ }
376+ return nil
377+}
378+
263379 var File_ermite_server_proto protoreflect.FileDescriptor
264380
265381 const file_ermite_server_proto_rawDesc = "" +
266382 "\n" +
267- "\x13ermite/server.proto\x12\x06ermite\"\x13\n" +
383+ "\x13ermite/server.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\x13\n" +
268384 "\x11GetVersionRequest\"J\n" +
269385 "\x12GetVersionResponse\x12\x18\n" +
270386 "\aversion\x18\x01 \x01(\tR\aversion\x12\x1a\n" +
@@ -276,11 +392,19 @@ const file_ermite_server_proto_rawDesc = "" +
276392 "\vfingerprint\x18\x02 \x01(\tR\vfingerprint\x12%\n" +
277393 "\x0ecommitter_name\x18\x03 \x01(\tR\rcommitterName\x12'\n" +
278394 "\x0fcommitter_email\x18\x04 \x01(\tR\x0ecommitterEmail\x12'\n" +
279- "\x0fallowed_signers\x18\x05 \x01(\tR\x0eallowedSigners2\xa2\x01\n" +
395+ "\x0fallowed_signers\x18\x05 \x01(\tR\x0eallowedSigners\"\x16\n" +
396+ "\x14RotateOIDCKeyRequest\"\xa3\x01\n" +
397+ "\x15RotateOIDCKeyResponse\x12\x16\n" +
398+ "\x06issuer\x18\x01 \x01(\tR\x06issuer\x12\x10\n" +
399+ "\x03kid\x18\x02 \x01(\tR\x03kid\x12\x1f\n" +
400+ "\vretired_kid\x18\x03 \x01(\tR\n" +
401+ "retiredKid\x12?\n" +
402+ "\rretired_until\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\fretiredUntil2\xf0\x01\n" +
280403 "\rServerService\x12C\n" +
281404 "\n" +
282405 "GetVersion\x12\x19.ermite.GetVersionRequest\x1a\x1a.ermite.GetVersionResponse\x12L\n" +
283- "\rGetSigningKey\x12\x1c.ermite.GetSigningKeyRequest\x1a\x1d.ermite.GetSigningKeyResponseBr\n" +
406+ "\rGetSigningKey\x12\x1c.ermite.GetSigningKeyRequest\x1a\x1d.ermite.GetSigningKeyResponse\x12L\n" +
407+ "\rRotateOIDCKey\x12\x1c.ermite.RotateOIDCKeyRequest\x1a\x1d.ermite.RotateOIDCKeyResponseBr\n" +
284408 "\n" +
285409 "com.ermiteB\vServerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"
286410
@@ -296,23 +420,29 @@ func file_ermite_server_proto_rawDescGZIP() []byte {
296420 return file_ermite_server_proto_rawDescData
297421 }
298422
299-var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
423+var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
300424 var file_ermite_server_proto_goTypes = []any{
301425 (*GetVersionRequest)(nil), // 0: ermite.GetVersionRequest
302426 (*GetVersionResponse)(nil), // 1: ermite.GetVersionResponse
303427 (*GetSigningKeyRequest)(nil), // 2: ermite.GetSigningKeyRequest
304428 (*GetSigningKeyResponse)(nil), // 3: ermite.GetSigningKeyResponse
429+ (*RotateOIDCKeyRequest)(nil), // 4: ermite.RotateOIDCKeyRequest
430+ (*RotateOIDCKeyResponse)(nil), // 5: ermite.RotateOIDCKeyResponse
431+ (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
305432 }
306433 var file_ermite_server_proto_depIdxs = []int32{
307- 0, // 0: ermite.ServerService.GetVersion:input_type -> ermite.GetVersionRequest
308- 2, // 1: ermite.ServerService.GetSigningKey:input_type -> ermite.GetSigningKeyRequest
309- 1, // 2: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse
310- 3, // 3: ermite.ServerService.GetSigningKey:output_type -> ermite.GetSigningKeyResponse
311- 2, // [2:4] is the sub-list for method output_type
312- 0, // [0:2] is the sub-list for method input_type
313- 0, // [0:0] is the sub-list for extension type_name
314- 0, // [0:0] is the sub-list for extension extendee
315- 0, // [0:0] is the sub-list for field type_name
434+ 6, // 0: ermite.RotateOIDCKeyResponse.retired_until:type_name -> google.protobuf.Timestamp
435+ 0, // 1: ermite.ServerService.GetVersion:input_type -> ermite.GetVersionRequest
436+ 2, // 2: ermite.ServerService.GetSigningKey:input_type -> ermite.GetSigningKeyRequest
437+ 4, // 3: ermite.ServerService.RotateOIDCKey:input_type -> ermite.RotateOIDCKeyRequest
438+ 1, // 4: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse
439+ 3, // 5: ermite.ServerService.GetSigningKey:output_type -> ermite.GetSigningKeyResponse
440+ 5, // 6: ermite.ServerService.RotateOIDCKey:output_type -> ermite.RotateOIDCKeyResponse
441+ 4, // [4:7] is the sub-list for method output_type
442+ 1, // [1:4] is the sub-list for method input_type
443+ 1, // [1:1] is the sub-list for extension type_name
444+ 1, // [1:1] is the sub-list for extension extendee
445+ 0, // [0:1] is the sub-list for field type_name
316446 }
317447
318448 func init() { file_ermite_server_proto_init() }
@@ -326,7 +456,7 @@ func file_ermite_server_proto_init() {
326456 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
327457 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_server_proto_rawDesc), len(file_ermite_server_proto_rawDesc)),
328458 NumEnums: 0,
329- NumMessages: 4,
459+ NumMessages: 6,
330460 NumExtensions: 0,
331461 NumServices: 1,
332462 },
@@ -11,6 +11,7 @@ package ermite
11 import (11 import (
12 protoreflect "google.golang.org/protobuf/reflect/protoreflect"12 protoreflect "google.golang.org/protobuf/reflect/protoreflect"
13 protoimpl "google.golang.org/protobuf/runtime/protoimpl"13 protoimpl "google.golang.org/protobuf/runtime/protoimpl"
14+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
14 reflect "reflect"15 reflect "reflect"
15 sync "sync"16 sync "sync"
16 unsafe "unsafe"17 unsafe "unsafe"
@@ -260,11 +261,126 @@ func (x *GetSigningKeyResponse) GetAllowedSigners() string {
260 return ""261 return ""
261 }262 }
262 263
264+// RotateOIDCKeyRequest is empty.
265+type RotateOIDCKeyRequest struct {
266+ state protoimpl.MessageState `protogen:"open.v1"`
267+ unknownFields protoimpl.UnknownFields
268+ sizeCache protoimpl.SizeCache
269+}
270+
271+func (x *RotateOIDCKeyRequest) Reset() {
272+ *x = RotateOIDCKeyRequest{}
273+ mi := &file_ermite_server_proto_msgTypes[4]
274+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
275+ ms.StoreMessageInfo(mi)
276+}
277+
278+func (x *RotateOIDCKeyRequest) String() string {
279+ return protoimpl.X.MessageStringOf(x)
280+}
281+
282+func (*RotateOIDCKeyRequest) ProtoMessage() {}
283+
284+func (x *RotateOIDCKeyRequest) ProtoReflect() protoreflect.Message {
285+ mi := &file_ermite_server_proto_msgTypes[4]
286+ if x != nil {
287+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
288+ if ms.LoadMessageInfo() == nil {
289+ ms.StoreMessageInfo(mi)
290+ }
291+ return ms
292+ }
293+ return mi.MessageOf(x)
294+}
295+
296+// Deprecated: Use RotateOIDCKeyRequest.ProtoReflect.Descriptor instead.
297+func (*RotateOIDCKeyRequest) Descriptor() ([]byte, []int) {
298+ return file_ermite_server_proto_rawDescGZIP(), []int{4}
299+}
300+
301+// RotateOIDCKeyResponse describes the new signing key of the ID tokens.
302+type RotateOIDCKeyResponse struct {
303+ state protoimpl.MessageState `protogen:"open.v1"`
304+ // Issuer is the "iss" of the ID tokens, for example
305+ // "https://git.example.com/-/oidc". Its discovery document is at
306+ // "<issuer>/.well-known/openid-configuration".
307+ Issuer string `protobuf:"bytes,1,opt,name=issuer,proto3" json:"issuer,omitempty"`
308+ // Kid is the key id of the new key, the "kid" of the tokens it signs: the
309+ // RFC 7638 thumbprint of its public key, base64url without padding.
310+ Kid string `protobuf:"bytes,2,opt,name=kid,proto3" json:"kid,omitempty"`
311+ // RetiredKid is the key id of the key it replaces; empty when there was
312+ // none.
313+ RetiredKid string `protobuf:"bytes,3,opt,name=retired_kid,json=retiredKid,proto3" json:"retired_kid,omitempty"`
314+ // RetiredUntil is when the replaced key leaves the JWKS. Unset when there
315+ // was none.
316+ RetiredUntil *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=retired_until,json=retiredUntil,proto3" json:"retired_until,omitempty"`
317+ unknownFields protoimpl.UnknownFields
318+ sizeCache protoimpl.SizeCache
319+}
320+
321+func (x *RotateOIDCKeyResponse) Reset() {
322+ *x = RotateOIDCKeyResponse{}
323+ mi := &file_ermite_server_proto_msgTypes[5]
324+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
325+ ms.StoreMessageInfo(mi)
326+}
327+
328+func (x *RotateOIDCKeyResponse) String() string {
329+ return protoimpl.X.MessageStringOf(x)
330+}
331+
332+func (*RotateOIDCKeyResponse) ProtoMessage() {}
333+
334+func (x *RotateOIDCKeyResponse) ProtoReflect() protoreflect.Message {
335+ mi := &file_ermite_server_proto_msgTypes[5]
336+ if x != nil {
337+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
338+ if ms.LoadMessageInfo() == nil {
339+ ms.StoreMessageInfo(mi)
340+ }
341+ return ms
342+ }
343+ return mi.MessageOf(x)
344+}
345+
346+// Deprecated: Use RotateOIDCKeyResponse.ProtoReflect.Descriptor instead.
347+func (*RotateOIDCKeyResponse) Descriptor() ([]byte, []int) {
348+ return file_ermite_server_proto_rawDescGZIP(), []int{5}
349+}
350+
351+func (x *RotateOIDCKeyResponse) GetIssuer() string {
352+ if x != nil {
353+ return x.Issuer
354+ }
355+ return ""
356+}
357+
358+func (x *RotateOIDCKeyResponse) GetKid() string {
359+ if x != nil {
360+ return x.Kid
361+ }
362+ return ""
363+}
364+
365+func (x *RotateOIDCKeyResponse) GetRetiredKid() string {
366+ if x != nil {
367+ return x.RetiredKid
368+ }
369+ return ""
370+}
371+
372+func (x *RotateOIDCKeyResponse) GetRetiredUntil() *timestamppb.Timestamp {
373+ if x != nil {
374+ return x.RetiredUntil
375+ }
376+ return nil
377+}
378+
263 var File_ermite_server_proto protoreflect.FileDescriptor379 var File_ermite_server_proto protoreflect.FileDescriptor
264 380
265 const file_ermite_server_proto_rawDesc = "" +381 const file_ermite_server_proto_rawDesc = "" +
266 "\n" +382 "\n" +
267- "\x13ermite/server.proto\x12\x06ermite\"\x13\n" +383+ "\x13ermite/server.proto\x12\x06ermite\x1a\x1fgoogle/protobuf/timestamp.proto\"\x13\n" +
268 "\x11GetVersionRequest\"J\n" +384 "\x11GetVersionRequest\"J\n" +
269 "\x12GetVersionResponse\x12\x18\n" +385 "\x12GetVersionResponse\x12\x18\n" +
270 "\aversion\x18\x01 \x01(\tR\aversion\x12\x1a\n" +386 "\aversion\x18\x01 \x01(\tR\aversion\x12\x1a\n" +
@@ -276,11 +392,19 @@ const file_ermite_server_proto_rawDesc = "" +
276 "\vfingerprint\x18\x02 \x01(\tR\vfingerprint\x12%\n" +392 "\vfingerprint\x18\x02 \x01(\tR\vfingerprint\x12%\n" +
277 "\x0ecommitter_name\x18\x03 \x01(\tR\rcommitterName\x12'\n" +393 "\x0ecommitter_name\x18\x03 \x01(\tR\rcommitterName\x12'\n" +
278 "\x0fcommitter_email\x18\x04 \x01(\tR\x0ecommitterEmail\x12'\n" +394 "\x0fcommitter_email\x18\x04 \x01(\tR\x0ecommitterEmail\x12'\n" +
279- "\x0fallowed_signers\x18\x05 \x01(\tR\x0eallowedSigners2\xa2\x01\n" +395+ "\x0fallowed_signers\x18\x05 \x01(\tR\x0eallowedSigners\"\x16\n" +
396+ "\x14RotateOIDCKeyRequest\"\xa3\x01\n" +
397+ "\x15RotateOIDCKeyResponse\x12\x16\n" +
398+ "\x06issuer\x18\x01 \x01(\tR\x06issuer\x12\x10\n" +
399+ "\x03kid\x18\x02 \x01(\tR\x03kid\x12\x1f\n" +
400+ "\vretired_kid\x18\x03 \x01(\tR\n" +
401+ "retiredKid\x12?\n" +
402+ "\rretired_until\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\fretiredUntil2\xf0\x01\n" +
280 "\rServerService\x12C\n" +403 "\rServerService\x12C\n" +
281 "\n" +404 "\n" +
282 "GetVersion\x12\x19.ermite.GetVersionRequest\x1a\x1a.ermite.GetVersionResponse\x12L\n" +405 "GetVersion\x12\x19.ermite.GetVersionRequest\x1a\x1a.ermite.GetVersionResponse\x12L\n" +
283- "\rGetSigningKey\x12\x1c.ermite.GetSigningKeyRequest\x1a\x1d.ermite.GetSigningKeyResponseBr\n" +406+ "\rGetSigningKey\x12\x1c.ermite.GetSigningKeyRequest\x1a\x1d.ermite.GetSigningKeyResponse\x12L\n" +
407+ "\rRotateOIDCKey\x12\x1c.ermite.RotateOIDCKeyRequest\x1a\x1d.ermite.RotateOIDCKeyResponseBr\n" +
284 "\n" +408 "\n" +
285 "com.ermiteB\vServerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"409 "com.ermiteB\vServerProtoP\x01Z\x1fbnema.dev/ermite-api/gen/ermite\xa2\x02\x03EXX\xaa\x02\x06Ermite\xca\x02\x06Ermite\xe2\x02\x12Ermite\\GPBMetadata\xea\x02\x06Ermiteb\x06proto3"
286 410
@@ -296,23 +420,29 @@ func file_ermite_server_proto_rawDescGZIP() []byte {
296 return file_ermite_server_proto_rawDescData420 return file_ermite_server_proto_rawDescData
297 }421 }
298 422
299-var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 4)423+var file_ermite_server_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
300 var file_ermite_server_proto_goTypes = []any{424 var file_ermite_server_proto_goTypes = []any{
301 (*GetVersionRequest)(nil), // 0: ermite.GetVersionRequest425 (*GetVersionRequest)(nil), // 0: ermite.GetVersionRequest
302 (*GetVersionResponse)(nil), // 1: ermite.GetVersionResponse426 (*GetVersionResponse)(nil), // 1: ermite.GetVersionResponse
303 (*GetSigningKeyRequest)(nil), // 2: ermite.GetSigningKeyRequest427 (*GetSigningKeyRequest)(nil), // 2: ermite.GetSigningKeyRequest
304 (*GetSigningKeyResponse)(nil), // 3: ermite.GetSigningKeyResponse428 (*GetSigningKeyResponse)(nil), // 3: ermite.GetSigningKeyResponse
429+ (*RotateOIDCKeyRequest)(nil), // 4: ermite.RotateOIDCKeyRequest
430+ (*RotateOIDCKeyResponse)(nil), // 5: ermite.RotateOIDCKeyResponse
431+ (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp
305 }432 }
306 var file_ermite_server_proto_depIdxs = []int32{433 var file_ermite_server_proto_depIdxs = []int32{
307- 0, // 0: ermite.ServerService.GetVersion:input_type -> ermite.GetVersionRequest434+ 6, // 0: ermite.RotateOIDCKeyResponse.retired_until:type_name -> google.protobuf.Timestamp
308- 2, // 1: ermite.ServerService.GetSigningKey:input_type -> ermite.GetSigningKeyRequest435+ 0, // 1: ermite.ServerService.GetVersion:input_type -> ermite.GetVersionRequest
309- 1, // 2: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse436+ 2, // 2: ermite.ServerService.GetSigningKey:input_type -> ermite.GetSigningKeyRequest
310- 3, // 3: ermite.ServerService.GetSigningKey:output_type -> ermite.GetSigningKeyResponse437+ 4, // 3: ermite.ServerService.RotateOIDCKey:input_type -> ermite.RotateOIDCKeyRequest
311- 2, // [2:4] is the sub-list for method output_type438+ 1, // 4: ermite.ServerService.GetVersion:output_type -> ermite.GetVersionResponse
312- 0, // [0:2] is the sub-list for method input_type439+ 3, // 5: ermite.ServerService.GetSigningKey:output_type -> ermite.GetSigningKeyResponse
313- 0, // [0:0] is the sub-list for extension type_name440+ 5, // 6: ermite.ServerService.RotateOIDCKey:output_type -> ermite.RotateOIDCKeyResponse
314- 0, // [0:0] is the sub-list for extension extendee441+ 4, // [4:7] is the sub-list for method output_type
315- 0, // [0:0] is the sub-list for field type_name442+ 1, // [1:4] is the sub-list for method input_type
443+ 1, // [1:1] is the sub-list for extension type_name
444+ 1, // [1:1] is the sub-list for extension extendee
445+ 0, // [0:1] is the sub-list for field type_name
316 }446 }
317 447
318 func init() { file_ermite_server_proto_init() }448 func init() { file_ermite_server_proto_init() }
@@ -326,7 +456,7 @@ func file_ermite_server_proto_init() {
326 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),456 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
327 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_server_proto_rawDesc), len(file_ermite_server_proto_rawDesc)),457 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_server_proto_rawDesc), len(file_ermite_server_proto_rawDesc)),
328 NumEnums: 0,458 NumEnums: 0,
329- NumMessages: 4,459+ NumMessages: 6,
330 NumExtensions: 0,460 NumExtensions: 0,
331 NumServices: 1,461 NumServices: 1,
332 },462 },
M gen_test.go +1 -0
@@ -47,6 +47,7 @@ func TestProcedures(t *testing.T) {
4747 {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},
4848 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},
4949 {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},
50+ {ermiteconnect.ServerServiceRotateOIDCKeyProcedure, "/ermite.ServerService/RotateOIDCKey"},
5051 {ermiteconnect.RunnerServiceRegisterRunnerProcedure, "/ermite.RunnerService/RegisterRunner"},
5152 {ermiteconnect.RunnerServiceFetchJobProcedure, "/ermite.RunnerService/FetchJob"},
5253 {ermiteconnect.RunnerServiceUpdateJobProcedure, "/ermite.RunnerService/UpdateJob"},
@@ -47,6 +47,7 @@ func TestProcedures(t *testing.T) {
47 {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},47 {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},
48 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},48 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},
49 {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},49 {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},
50+ {ermiteconnect.ServerServiceRotateOIDCKeyProcedure, "/ermite.ServerService/RotateOIDCKey"},
50 {ermiteconnect.RunnerServiceRegisterRunnerProcedure, "/ermite.RunnerService/RegisterRunner"},51 {ermiteconnect.RunnerServiceRegisterRunnerProcedure, "/ermite.RunnerService/RegisterRunner"},
51 {ermiteconnect.RunnerServiceFetchJobProcedure, "/ermite.RunnerService/FetchJob"},52 {ermiteconnect.RunnerServiceFetchJobProcedure, "/ermite.RunnerService/FetchJob"},
52 {ermiteconnect.RunnerServiceUpdateJobProcedure, "/ermite.RunnerService/UpdateJob"},53 {ermiteconnect.RunnerServiceUpdateJobProcedure, "/ermite.RunnerService/UpdateJob"},
M proto/ermite/runner.proto +24 -0
@@ -597,6 +597,30 @@ message Job {
597597 // this in the log of "Set up job". False from servers that do not send it:
598598 // the safe reading.
599599 bool trusted = 21;
600+ // IdTokenUrl is the URL the job's code fetches OIDC ID tokens from, the
601+ // ACTIONS_ID_TOKEN_REQUEST_URL of GitHub's runner: the server's
602+ // "/-/oidc/token?api-version=2.0". It already has a query, so clients append
603+ // "&audience=<url-encoded audience>" as @actions/core does. A GET with
604+ // "Authorization: Bearer <id_token_request_token>" answers
605+ // {"value":"<jwt>"}: an RS256 JWT with GitHub's claims, signed by the key
606+ // the server publishes at "/-/oidc/.well-known/jwks". The runner sets
607+ // ACTIONS_ID_TOKEN_REQUEST_URL to it for run steps, JavaScript actions and
608+ // Docker actions, with the origin rewritten as for the other server URLs
609+ // the sandbox reaches. Empty when the job may not have ID tokens: when it is
610+ // not trusted, when its effective permissions do not include
611+ // "id-token: write", when the server has no key to sign with, and from
612+ // servers without the feature "ci.oidc". Both fields are set or neither.
613+ string id_token_url = 22;
614+ // IdTokenRequestToken is the bearer for id_token_url, the
615+ // ACTIONS_ID_TOKEN_REQUEST_TOKEN of GitHub's runner: "ermiteoidc_" and 43
616+ // base64url characters (32 random bytes, no padding). It is valid only for
617+ // id_token_url, only while the job is assigned or running (it is revoked
618+ // when the job ends, on cancellation and when the job is lost), and is
619+ // shown only in this response, or in a repeat of the same poll_id. It is a
620+ // secret: the runner registers it in its masker before anything runs, and
621+ // gives it to the sandbox only with id_token_url. Empty when id_token_url
622+ // is.
623+ string id_token_request_token = 23;
600624 }
601625
602626 // JobPermissions are the permissions of the job repository token, with the
@@ -597,6 +597,30 @@ message Job {
597 // this in the log of "Set up job". False from servers that do not send it:597 // this in the log of "Set up job". False from servers that do not send it:
598 // the safe reading.598 // the safe reading.
599 bool trusted = 21;599 bool trusted = 21;
600+ // IdTokenUrl is the URL the job's code fetches OIDC ID tokens from, the
601+ // ACTIONS_ID_TOKEN_REQUEST_URL of GitHub's runner: the server's
602+ // "/-/oidc/token?api-version=2.0". It already has a query, so clients append
603+ // "&audience=<url-encoded audience>" as @actions/core does. A GET with
604+ // "Authorization: Bearer <id_token_request_token>" answers
605+ // {"value":"<jwt>"}: an RS256 JWT with GitHub's claims, signed by the key
606+ // the server publishes at "/-/oidc/.well-known/jwks". The runner sets
607+ // ACTIONS_ID_TOKEN_REQUEST_URL to it for run steps, JavaScript actions and
608+ // Docker actions, with the origin rewritten as for the other server URLs
609+ // the sandbox reaches. Empty when the job may not have ID tokens: when it is
610+ // not trusted, when its effective permissions do not include
611+ // "id-token: write", when the server has no key to sign with, and from
612+ // servers without the feature "ci.oidc". Both fields are set or neither.
613+ string id_token_url = 22;
614+ // IdTokenRequestToken is the bearer for id_token_url, the
615+ // ACTIONS_ID_TOKEN_REQUEST_TOKEN of GitHub's runner: "ermiteoidc_" and 43
616+ // base64url characters (32 random bytes, no padding). It is valid only for
617+ // id_token_url, only while the job is assigned or running (it is revoked
618+ // when the job ends, on cancellation and when the job is lost), and is
619+ // shown only in this response, or in a repeat of the same poll_id. It is a
620+ // secret: the runner registers it in its masker before anything runs, and
621+ // gives it to the sandbox only with id_token_url. Empty when id_token_url
622+ // is.
623+ string id_token_request_token = 23;
600 }624 }
601 625
602 // JobPermissions are the permissions of the job repository token, with the626 // JobPermissions are the permissions of the job repository token, with the
M proto/ermite/server.proto +33 -0
@@ -4,6 +4,8 @@ syntax = "proto3";
44
55 package ermite;
66
7+import "google/protobuf/timestamp.proto";
8+
79 // ServerService describes the server itself.
810 service ServerService {
911 // GetVersion returns the server version and the optional capabilities of the
@@ -16,6 +18,17 @@ service ServerService {
1618 // Servers that implement it list "signing_key" in
1719 // GetVersionResponse.features.
1820 rpc GetSigningKey(GetSigningKeyRequest) returns (GetSigningKeyResponse);
21+ // RotateOIDCKey replaces the key with which the server signs the OIDC ID
22+ // tokens of CI jobs (Job.id_token_url) by a new one. The previous key stops
23+ // signing at once but stays in the server's JWKS
24+ // ("/-/oidc/.well-known/jwks") for 24 hours, so that tokens it signed still
25+ // verify until they expire. It needs owner rights: the admin token, the
26+ // owner's session, or a token with every scope and no repository list or
27+ // branch prefixes (permission_denied otherwise). It fails with
28+ // failed_precondition when the server issues no ID tokens (it has no
29+ // secrets key to seal the key with). Servers that implement it list
30+ // "ci.oidc" in GetVersionResponse.features.
31+ rpc RotateOIDCKey(RotateOIDCKeyRequest) returns (RotateOIDCKeyResponse);
1932 }
2033
2134 // GetVersionRequest is empty.
@@ -71,3 +84,23 @@ message GetSigningKeyResponse {
7184 // "git config gpg.ssh.allowedSignersFile".
7285 string allowed_signers = 5;
7386 }
87+
88+// RotateOIDCKeyRequest is empty.
89+message RotateOIDCKeyRequest {}
90+
91+// RotateOIDCKeyResponse describes the new signing key of the ID tokens.
92+message RotateOIDCKeyResponse {
93+ // Issuer is the "iss" of the ID tokens, for example
94+ // "https://git.example.com/-/oidc". Its discovery document is at
95+ // "<issuer>/.well-known/openid-configuration".
96+ string issuer = 1;
97+ // Kid is the key id of the new key, the "kid" of the tokens it signs: the
98+ // RFC 7638 thumbprint of its public key, base64url without padding.
99+ string kid = 2;
100+ // RetiredKid is the key id of the key it replaces; empty when there was
101+ // none.
102+ string retired_kid = 3;
103+ // RetiredUntil is when the replaced key leaves the JWKS. Unset when there
104+ // was none.
105+ google.protobuf.Timestamp retired_until = 4;
106+}
@@ -4,6 +4,8 @@ syntax = "proto3";
4 4
5 package ermite;5 package ermite;
6 6
7+import "google/protobuf/timestamp.proto";
8+
7 // ServerService describes the server itself.9 // ServerService describes the server itself.
8 service ServerService {10 service ServerService {
9 // GetVersion returns the server version and the optional capabilities of the11 // GetVersion returns the server version and the optional capabilities of the
@@ -16,6 +18,17 @@ service ServerService {
16 // Servers that implement it list "signing_key" in18 // Servers that implement it list "signing_key" in
17 // GetVersionResponse.features.19 // GetVersionResponse.features.
18 rpc GetSigningKey(GetSigningKeyRequest) returns (GetSigningKeyResponse);20 rpc GetSigningKey(GetSigningKeyRequest) returns (GetSigningKeyResponse);
21+ // RotateOIDCKey replaces the key with which the server signs the OIDC ID
22+ // tokens of CI jobs (Job.id_token_url) by a new one. The previous key stops
23+ // signing at once but stays in the server's JWKS
24+ // ("/-/oidc/.well-known/jwks") for 24 hours, so that tokens it signed still
25+ // verify until they expire. It needs owner rights: the admin token, the
26+ // owner's session, or a token with every scope and no repository list or
27+ // branch prefixes (permission_denied otherwise). It fails with
28+ // failed_precondition when the server issues no ID tokens (it has no
29+ // secrets key to seal the key with). Servers that implement it list
30+ // "ci.oidc" in GetVersionResponse.features.
31+ rpc RotateOIDCKey(RotateOIDCKeyRequest) returns (RotateOIDCKeyResponse);
19 }32 }
20 33
21 // GetVersionRequest is empty.34 // GetVersionRequest is empty.
@@ -71,3 +84,23 @@ message GetSigningKeyResponse {
71 // "git config gpg.ssh.allowedSignersFile".84 // "git config gpg.ssh.allowedSignersFile".
72 string allowed_signers = 5;85 string allowed_signers = 5;
73 }86 }
87+
88+// RotateOIDCKeyRequest is empty.
89+message RotateOIDCKeyRequest {}
90+
91+// RotateOIDCKeyResponse describes the new signing key of the ID tokens.
92+message RotateOIDCKeyResponse {
93+ // Issuer is the "iss" of the ID tokens, for example
94+ // "https://git.example.com/-/oidc". Its discovery document is at
95+ // "<issuer>/.well-known/openid-configuration".
96+ string issuer = 1;
97+ // Kid is the key id of the new key, the "kid" of the tokens it signs: the
98+ // RFC 7638 thumbprint of its public key, base64url without padding.
99+ string kid = 2;
100+ // RetiredKid is the key id of the key it replaces; empty when there was
101+ // none.
102+ string retired_kid = 3;
103+ // RetiredUntil is when the replaced key leaves the JWKS. Unset when there
104+ // was none.
105+ google.protobuf.Timestamp retired_until = 4;
106+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 18

↑↓ moveenter openesc close