Skip to content

Commit 6438e07

Browse files
authored
Merge pull request #2 from jsiebens/id_token
feat: use wip tailscale id token
2 parents a732a42 + c9d3170 commit 6438e07

File tree

7 files changed

+215
-153
lines changed

7 files changed

+215
-153
lines changed

cmd/agent/tailscale_attestor/main.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,13 @@
11
package main
22

33
import (
4+
"github.com/jsiebens/spire-tailscale-plugin/pkg/agent/plugin/nodeattestor/ts"
45
"github.com/spiffe/spire-plugin-sdk/pluginmain"
56
nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/agent/nodeattestor/v1"
6-
"tailscale.com/client/tailscale"
77
)
88

9-
type Plugin struct {
10-
nodeattestorv1.UnimplementedNodeAttestorServer
11-
}
12-
13-
func (p *Plugin) AidAttestation(stream nodeattestorv1.NodeAttestor_AidAttestationServer) error {
14-
status, err := tailscale.Status(stream.Context())
15-
if err != nil {
16-
return err
17-
}
18-
19-
payload, err := status.Self.PublicKey.MarshalText()
20-
if err != nil {
21-
return err
22-
}
23-
24-
return stream.Send(&nodeattestorv1.PayloadOrChallengeResponse{
25-
Data: &nodeattestorv1.PayloadOrChallengeResponse_Payload{
26-
Payload: payload,
27-
},
28-
})
29-
}
30-
319
func main() {
32-
plugin := new(Plugin)
10+
plugin := ts.New()
3311
pluginmain.Serve(
3412
nodeattestorv1.NodeAttestorPluginServer(plugin),
3513
)

cmd/server/tailscale_attestor/main.go

Lines changed: 2 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,14 @@
11
package main
22

33
import (
4-
"context"
5-
"fmt"
6-
"github.com/hashicorp/hcl"
7-
"github.com/spiffe/go-spiffe/v2/spiffeid"
4+
"github.com/jsiebens/spire-tailscale-plugin/pkg/server/plugin/nodeattestor/ts"
85
"github.com/spiffe/spire-plugin-sdk/pluginmain"
96
nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/server/nodeattestor/v1"
107
configv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/service/common/config/v1"
11-
"google.golang.org/grpc/codes"
12-
"google.golang.org/grpc/status"
13-
"sync"
14-
"tailscale.com/client/tailscale"
15-
"tailscale.com/ipn/ipnstate"
16-
"tailscale.com/types/key"
178
)
189

19-
const (
20-
PluginName = "ts"
21-
)
22-
23-
type Config struct {
24-
trustDomain spiffeid.TrustDomain
25-
}
26-
27-
// Plugin implements the NodeAttestor plugin
28-
type Plugin struct {
29-
nodeattestorv1.UnimplementedNodeAttestorServer
30-
configv1.UnimplementedConfigServer
31-
32-
configMtx sync.RWMutex
33-
config *Config
34-
}
35-
36-
func (p *Plugin) Attest(stream nodeattestorv1.NodeAttestor_AttestServer) error {
37-
req, err := stream.Recv()
38-
if err != nil {
39-
return err
40-
}
41-
42-
c, err := p.getConfig()
43-
if err != nil {
44-
return err
45-
}
46-
47-
payload := req.GetPayload()
48-
49-
clientKey := key.NodePublic{}
50-
if err := clientKey.UnmarshalText(payload); err != nil {
51-
return err
52-
}
53-
54-
tsStatus, err := tailscale.Status(stream.Context())
55-
if err != nil {
56-
return fmt.Errorf("failed to query local tailscaled status: %w", err)
57-
}
58-
59-
var node *ipnstate.PeerStatus
60-
61-
if clientKey == tsStatus.Self.PublicKey {
62-
node = tsStatus.Self
63-
}
64-
65-
if p, exists := tsStatus.Peer[clientKey]; exists {
66-
node = p
67-
}
68-
69-
if node == nil {
70-
return fmt.Errorf("unable to find provided client key")
71-
}
72-
73-
id, err := agentID(c.trustDomain, fmt.Sprintf("/%s/%s", PluginName, node.DNSName))
74-
if err != nil {
75-
return err
76-
}
77-
78-
return stream.Send(&nodeattestorv1.AttestResponse{
79-
Response: &nodeattestorv1.AttestResponse_AgentAttributes{
80-
AgentAttributes: &nodeattestorv1.AgentAttributes{
81-
SpiffeId: id.String(),
82-
},
83-
},
84-
})
85-
}
86-
87-
func (p *Plugin) Configure(ctx context.Context, req *configv1.ConfigureRequest) (*configv1.ConfigureResponse, error) {
88-
hclConfig := new(Config)
89-
if err := hcl.Decode(hclConfig, req.HclConfiguration); err != nil {
90-
return nil, status.Errorf(codes.InvalidArgument, "unable to decode configuration: %v", err)
91-
}
92-
93-
if req.CoreConfiguration == nil {
94-
return nil, status.Error(codes.InvalidArgument, "global configuration is required")
95-
}
96-
97-
if req.CoreConfiguration.TrustDomain == "" {
98-
return nil, status.Error(codes.InvalidArgument, "trust_domain is required")
99-
}
100-
101-
trustDomain, err := spiffeid.TrustDomainFromString(req.CoreConfiguration.TrustDomain)
102-
if err != nil {
103-
return nil, status.Errorf(codes.InvalidArgument, "trust_domain is invalid: %v", err)
104-
}
105-
106-
hclConfig.trustDomain = trustDomain
107-
108-
p.setConfig(hclConfig)
109-
return &configv1.ConfigureResponse{}, nil
110-
}
111-
112-
func (p *Plugin) setConfig(config *Config) {
113-
p.configMtx.Lock()
114-
p.config = config
115-
p.configMtx.Unlock()
116-
}
117-
118-
func (p *Plugin) getConfig() (*Config, error) {
119-
p.configMtx.RLock()
120-
defer p.configMtx.RUnlock()
121-
if p.config == nil {
122-
return nil, status.Error(codes.FailedPrecondition, "not configured")
123-
}
124-
return p.config, nil
125-
}
126-
127-
func agentID(td spiffeid.TrustDomain, suffix string) (spiffeid.ID, error) {
128-
if td.IsZero() {
129-
return spiffeid.ID{}, fmt.Errorf("cannot create agent ID with suffix %q for empty trust domain", suffix)
130-
}
131-
if err := spiffeid.ValidatePath(suffix); err != nil {
132-
return spiffeid.ID{}, fmt.Errorf("invalid agent path suffix %q: %w", suffix, err)
133-
}
134-
return spiffeid.FromPath(td, "/spire/agent"+suffix)
135-
}
136-
13710
func main() {
138-
plugin := new(Plugin)
11+
plugin := ts.New()
13912
pluginmain.Serve(
14013
nodeattestorv1.NodeAttestorPluginServer(plugin),
14114
configv1.ConfigServiceServer(plugin),

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/jsiebens/spire-tailscale-plugin
33
go 1.18
44

55
require (
6+
github.com/coreos/go-oidc/v3 v3.1.0
67
github.com/hashicorp/hcl v1.0.0
78
github.com/spiffe/go-spiffe/v2 v2.0.0
89
github.com/spiffe/spire-plugin-sdk v1.2.2
@@ -33,11 +34,14 @@ require (
3334
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
3435
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
3536
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 // indirect
37+
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
3638
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
3739
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
3840
golang.org/x/text v0.3.7 // indirect
3941
golang.zx2c4.com/wireguard/windows v0.4.10 // indirect
42+
google.golang.org/appengine v1.4.0 // indirect
4043
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 // indirect
4144
google.golang.org/protobuf v1.27.1 // indirect
45+
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
4246
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 // indirect
4347
)

go.sum

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
1818
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
1919
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
2020
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
21+
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
22+
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
2123
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
2224
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2325
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -154,6 +156,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
154156
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
155157
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
156158
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
159+
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
157160
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
158161
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
159162
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
@@ -163,6 +166,7 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
163166
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c=
164167
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
165168
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
169+
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
166170
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
167171
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
168172
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -218,6 +222,7 @@ golang.zx2c4.com/wireguard v0.0.0-20210905140043-2ef39d47540c/go.mod h1:laHzsbfM
218222
golang.zx2c4.com/wireguard/windows v0.4.10 h1:HmjzJnb+G4NCdX+sfjsQlsxGPuYaThxRbZUZFLyR0/s=
219223
golang.zx2c4.com/wireguard/windows v0.4.10/go.mod h1:v7w/8FC48tTBm1IzScDVPEEb0/GjLta+T0ybpP9UWRg=
220224
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
225+
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
221226
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
222227
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
223228
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -258,6 +263,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
258263
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
259264
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
260265
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
266+
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
267+
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
261268
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
262269
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
263270
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package ts
2+
3+
import (
4+
"github.com/jsiebens/spire-tailscale-plugin/pkg/common/plugin/ts"
5+
nodeattestorv1 "github.com/spiffe/spire-plugin-sdk/proto/spire/plugin/agent/nodeattestor/v1"
6+
"google.golang.org/grpc/codes"
7+
"google.golang.org/grpc/status"
8+
"tailscale.com/client/tailscale"
9+
)
10+
11+
const (
12+
tokenAudience = ts.TokenAudience
13+
)
14+
15+
type TSAttestorPlugin struct {
16+
nodeattestorv1.UnimplementedNodeAttestorServer
17+
}
18+
19+
func New() *TSAttestorPlugin {
20+
return &TSAttestorPlugin{}
21+
}
22+
23+
func (p *TSAttestorPlugin) AidAttestation(stream nodeattestorv1.NodeAttestor_AidAttestationServer) error {
24+
token, err := tailscale.IDToken(stream.Context(), tokenAudience)
25+
if err != nil {
26+
return status.Errorf(codes.Internal, "unable to retrieve valid identity token: %v", err)
27+
}
28+
29+
return stream.Send(&nodeattestorv1.PayloadOrChallengeResponse{
30+
Data: &nodeattestorv1.PayloadOrChallengeResponse_Payload{
31+
Payload: []byte(token.IDToken),
32+
},
33+
})
34+
}

pkg/common/plugin/ts/common.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package ts
2+
3+
const (
4+
PluginName = "tailscale"
5+
TokenAudience = "spire-tailscale-node-attestor"
6+
)
7+
8+
type TailscaleClaims struct {
9+
Node string `json:"node"`
10+
Domain string `json:"domain"`
11+
}

0 commit comments

Comments
 (0)