@@ -95,8 +95,9 @@ type Workload struct {
9595}
9696
9797type ConsulPermissions struct {
98- KeyPrefixes []string `json:"key_prefixes,omitempty"`
99- SessionWrite bool `json:"session_write,omitempty"`
98+ KeyPrefixes []string `json:"key_prefixes,omitempty"`
99+ SessionWrite bool `json:"session_write,omitempty"`
100+ AgentReadSelf bool `json:"agent_read_self,omitempty"`
100101}
101102
102103func ParsePolicy (b []byte ) (* Policy , error ) {
@@ -243,7 +244,8 @@ func (s *Server) handleAttest(w http.ResponseWriter, r *http.Request) {
243244 }
244245
245246 binding := firstNonEmpty (req .Binding , req .CertPubKey )
246- if err := validateBinding (req .Identity , binding ); err != nil {
247+ bindingStatement , err := parseBindingStatement (req .Identity , binding )
248+ if err != nil {
247249 writeError (w , http .StatusBadRequest , "BAD_REQUEST" , err .Error ())
248250 return
249251 }
@@ -291,6 +293,7 @@ func (s *Server) handleAttest(w http.ResponseWriter, r *http.Request) {
291293 Description : fmt .Sprintf ("attested %s %s" , workload .WorkloadID , verified .ComposeHash ),
292294 ConsulService : workload .ConsulService ,
293295 ConsulPermissions : workload .ConsulPermissions ,
296+ PeerID : bindingStatement .PeerID ,
294297 TTL : s .tokenTTL ,
295298 })
296299 if err != nil {
@@ -339,24 +342,36 @@ func rawJSONAsString(raw json.RawMessage) string {
339342 return string (raw )
340343}
341344
342- func validateBinding (identity , bindingHex string ) error {
345+ type BindingStatement struct {
346+ Version int `json:"version"`
347+ Identity string `json:"identity"`
348+ Cluster string `json:"cluster,omitempty"`
349+ PeerID string `json:"peer_id,omitempty"`
350+ AppID string `json:"app_id,omitempty"`
351+ InstanceID string `json:"instance_id,omitempty"`
352+ ComposeHash string `json:"compose_hash,omitempty"`
353+ IssuedAt string `json:"issued_at,omitempty"`
354+ }
355+
356+ func parseBindingStatement (identity , bindingHex string ) (* BindingStatement , error ) {
343357 if bindingHex == "" {
344- return errors .New ("binding is required" )
358+ return nil , errors .New ("binding is required" )
345359 }
346360 binding , err := hex .DecodeString (strings .TrimPrefix (strings .ToLower (strings .TrimSpace (bindingHex )), "0x" ))
347361 if err != nil {
348- return fmt .Errorf ("binding must be hex: %w" , err )
362+ return nil , fmt .Errorf ("binding must be hex: %w" , err )
349363 }
350- var statement struct {
351- Identity string `json:"identity"`
364+ if len ( binding ) == 0 {
365+ return nil , errors . New ( "binding must not be empty" )
352366 }
367+ var statement BindingStatement
353368 if err := json .Unmarshal (binding , & statement ); err != nil {
354- return fmt .Errorf ("binding must be canonical JSON statement bytes encoded as hex: %w" , err )
369+ return nil , fmt .Errorf ("binding must be canonical JSON statement bytes encoded as hex: %w" , err )
355370 }
356371 if statement .Identity != identity {
357- return fmt .Errorf ("binding identity %q does not match request identity %q" , statement .Identity , identity )
372+ return nil , fmt .Errorf ("binding identity %q does not match request identity %q" , statement .Identity , identity )
358373 }
359- return nil
374+ return & statement , nil
360375}
361376
362377type NonceStore struct {
@@ -503,6 +518,7 @@ type TokenRequest struct {
503518 Description string
504519 ConsulService string
505520 ConsulPermissions ConsulPermissions
521+ PeerID string
506522 TTL time.Duration
507523}
508524
@@ -527,8 +543,12 @@ func (i *ConsulIssuer) IssueToken(ctx context.Context, req TokenRequest) (string
527543 {"ServiceName" : req .ConsulService },
528544 },
529545 }
530- if rules := consulACLRules (req .ConsulPermissions ); rules != "" {
531- policyName , err := i .EnsurePolicy (ctx , "attested-dcs-" + shortSHA256 (rules , 16 ), "DCS permissions for attested workloads" , rules )
546+ rules , err := consulACLRules (req .ConsulPermissions , req .PeerID )
547+ if err != nil {
548+ return "" , err
549+ }
550+ if rules != "" {
551+ policyName , err := i .EnsurePolicy (ctx , "attested-workload-" + shortSHA256 (rules , 16 ), "Additional permissions for attested workloads" , rules )
532552 if err != nil {
533553 return "" , err
534554 }
@@ -663,8 +683,15 @@ func (e *consulHTTPError) Error() string {
663683 return fmt .Sprintf ("consul returned HTTP %d: %s" , e .StatusCode , e .Body )
664684}
665685
666- func consulACLRules (p ConsulPermissions ) string {
686+ func consulACLRules (p ConsulPermissions , peerID string ) ( string , error ) {
667687 var b strings.Builder
688+ if p .AgentReadSelf {
689+ peerID = strings .TrimSpace (peerID )
690+ if err := validateConsulAgentName (peerID ); err != nil {
691+ return "" , fmt .Errorf ("agent_read_self requires attested peer_id: %w" , err )
692+ }
693+ fmt .Fprintf (& b , "agent %q {\n policy = \" read\" \n }\n " , peerID )
694+ }
668695 for _ , prefix := range p .KeyPrefixes {
669696 prefix = strings .Trim (prefix , "/" )
670697 if prefix == "" {
@@ -675,7 +702,20 @@ func consulACLRules(p ConsulPermissions) string {
675702 if p .SessionWrite {
676703 b .WriteString ("session_prefix \" \" {\n policy = \" write\" \n }\n " )
677704 }
678- return b .String ()
705+ return b .String (), nil
706+ }
707+
708+ func validateConsulAgentName (name string ) error {
709+ if name == "" {
710+ return errors .New ("peer_id is empty" )
711+ }
712+ for _ , r := range name {
713+ if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' || r == '.' {
714+ continue
715+ }
716+ return fmt .Errorf ("peer_id %q contains unsupported character %q" , name , r )
717+ }
718+ return nil
679719}
680720
681721func shortSHA256 (s string , hexChars int ) string {
0 commit comments