Skip to content

Commit 65263a9

Browse files
authored
Merge pull request #51 from dberkov/token
Adding support for server to authenticate agent
2 parents 8503646 + 32635ed commit 65263a9

File tree

19 files changed

+855
-68
lines changed

19 files changed

+855
-68
lines changed

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ DOCKER_CLI_EXPERIMENTAL ?= enabled
3434
## --------------------------------------
3535
## Testing
3636
## --------------------------------------
37+
mock_gen:
38+
mkdir -p proto/agent/mocks
39+
mockgen sigs.k8s.io/apiserver-network-proxy/proto/agent AgentService_ConnectServer > proto/agent/mocks/agent_mock.go
40+
cat hack/go-license-header.txt proto/agent/mocks/agent_mock.go > proto/agent/mocks/agent_mock.licensed.go
41+
mv proto/agent/mocks/agent_mock.licensed.go proto/agent/mocks/agent_mock.go
3742

3843
.PHONY: test
3944
test:
@@ -68,7 +73,7 @@ bin/proxy-server: bin cmd/proxy/main.go proto/agent/agent.pb.go proto/proxy.pb.g
6873
## --------------------------------------
6974

7075
.PHONY: gen
71-
gen: proto/agent/agent.pb.go proto/proxy.pb.go
76+
gen: proto/agent/agent.pb.go proto/proxy.pb.go mock_gen
7277

7378
proto/agent/agent.pb.go: proto/agent/agent.proto
7479
protoc -I proto proto/agent/agent.proto --go_out=plugins=grpc:proto
@@ -244,4 +249,4 @@ release-alias-tag: # Adds the tag to the last build tag. BASE_REF comes from the
244249

245250
.PHONY: clean
246251
clean:
247-
rm -rf proto/agent/agent.pb.go proto/proxy.pb.go easy-rsa.tar.gz easy-rsa-master cfssl cfssljson certs bin
252+
rm -rf proto/agent/agent.pb.go proto/proxy.pb.go easy-rsa.tar.gz easy-rsa-master cfssl cfssljson certs bin proto/agent/mocks

cmd/agent/main.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,20 @@ type GrpcProxyAgentOptions struct {
6868
syncInterval time.Duration
6969
probeInterval time.Duration
7070
reconnectInterval time.Duration
71+
72+
// file contains service account authorization token for enabling proxy-server token based authorization
73+
serviceAccountTokenPath string
7174
}
7275

7376
func (o *GrpcProxyAgentOptions) ClientSetConfig(dialOption grpc.DialOption) *agentclient.ClientSetConfig {
7477
return &agentclient.ClientSetConfig{
75-
Address: fmt.Sprintf("%s:%d", o.proxyServerHost, o.proxyServerPort),
76-
AgentID: o.agentID,
77-
SyncInterval: o.syncInterval,
78-
ProbeInterval: o.probeInterval,
79-
ReconnectInterval: o.reconnectInterval,
80-
DialOption: dialOption,
78+
Address: fmt.Sprintf("%s:%d", o.proxyServerHost, o.proxyServerPort),
79+
AgentID: o.agentID,
80+
SyncInterval: o.syncInterval,
81+
ProbeInterval: o.probeInterval,
82+
ReconnectInterval: o.reconnectInterval,
83+
DialOption: dialOption,
84+
ServiceAccountTokenPath: o.serviceAccountTokenPath,
8185
}
8286
}
8387

@@ -92,6 +96,7 @@ func (o *GrpcProxyAgentOptions) Flags() *pflag.FlagSet {
9296
flags.DurationVar(&o.syncInterval, "sync-interval", o.syncInterval, "The interval by which the agent periodically checks that it has connections to all instances of the proxy server.")
9397
flags.DurationVar(&o.probeInterval, "probe-interval", o.probeInterval, "The interval by which the agent periodically checks if its connections to the proxy server are ready.")
9498
flags.DurationVar(&o.reconnectInterval, "reconnect-interval", o.reconnectInterval, "The interval by which the agent tries to reconnect.")
99+
flags.StringVar(&o.serviceAccountTokenPath, "service-account-token-path", o.serviceAccountTokenPath, "If non-empty proxy agent uses this token to prove its identity to the proxy server.")
95100
return flags
96101
}
97102

@@ -105,6 +110,7 @@ func (o *GrpcProxyAgentOptions) Print() {
105110
klog.Warningf("SyncInterval set to %v.\n", o.syncInterval)
106111
klog.Warningf("ProbeInterval set to %v.\n", o.probeInterval)
107112
klog.Warningf("ReconnectInterval set to %v.\n", o.reconnectInterval)
113+
klog.Warningf("ServiceAccountTokenPath set to \"%s\".\n", o.serviceAccountTokenPath)
108114
}
109115

110116
func (o *GrpcProxyAgentOptions) Validate() error {
@@ -132,20 +138,26 @@ func (o *GrpcProxyAgentOptions) Validate() error {
132138
if o.proxyServerPort <= 0 {
133139
return fmt.Errorf("proxy server port %d must be greater than 0", o.proxyServerPort)
134140
}
141+
if o.serviceAccountTokenPath != "" {
142+
if _, err := os.Stat(o.serviceAccountTokenPath); os.IsNotExist(err) {
143+
return fmt.Errorf("error checking service account token path %s, got %v", o.serviceAccountTokenPath, err)
144+
}
145+
}
135146
return nil
136147
}
137148

138149
func newGrpcProxyAgentOptions() *GrpcProxyAgentOptions {
139150
o := GrpcProxyAgentOptions{
140-
agentCert: "",
141-
agentKey: "",
142-
caCert: "",
143-
proxyServerHost: "127.0.0.1",
144-
proxyServerPort: 8091,
145-
agentID: uuid.New().String(),
146-
syncInterval: 5 * time.Second,
147-
probeInterval: 5 * time.Second,
148-
reconnectInterval: 5 * time.Second,
151+
agentCert: "",
152+
agentKey: "",
153+
caCert: "",
154+
proxyServerHost: "127.0.0.1",
155+
proxyServerPort: 8091,
156+
agentID: uuid.New().String(),
157+
syncInterval: 5 * time.Second,
158+
probeInterval: 5 * time.Second,
159+
reconnectInterval: 5 * time.Second,
160+
serviceAccountTokenPath: "",
149161
}
150162
return &o
151163
}

cmd/proxy/main.go

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ import (
2929
"os/signal"
3030
"syscall"
3131

32-
"k8s.io/klog"
33-
3432
"github.com/google/uuid"
3533
"github.com/prometheus/client_golang/prometheus"
3634
"github.com/spf13/cobra"
3735
"github.com/spf13/pflag"
3836
"google.golang.org/grpc"
3937
"google.golang.org/grpc/credentials"
38+
"k8s.io/klog"
39+
"k8s.io/client-go/kubernetes"
40+
"k8s.io/client-go/tools/clientcmd"
4041
"sigs.k8s.io/apiserver-network-proxy/pkg/agent/agentserver"
4142
"sigs.k8s.io/apiserver-network-proxy/pkg/util"
4243
"sigs.k8s.io/apiserver-network-proxy/proto/agent"
@@ -86,6 +87,14 @@ type ProxyRunOptions struct {
8687
serverID string
8788
// Number of proxy server instances, should be 1 unless it is a HA proxy server.
8889
serverCount uint
90+
// Agent pod's namespace for token-based agent authentication
91+
agentNamespace string
92+
// Agent pod's service account for token-based agent authentication
93+
agentServiceAccount string
94+
// Token's audience for token-based agent authentication
95+
authenticationAudience string
96+
// Path to kubeconfig (used by kubernetes client)
97+
kubeconfigPath string
8998
}
9099

91100
func (o *ProxyRunOptions) Flags() *pflag.FlagSet {
@@ -103,6 +112,10 @@ func (o *ProxyRunOptions) Flags() *pflag.FlagSet {
103112
flags.UintVar(&o.adminPort, "admin-port", o.adminPort, "Port we listen for admin connections on.")
104113
flags.StringVar(&o.serverID, "server-id", o.serverID, "The unique ID of this server.")
105114
flags.UintVar(&o.serverCount, "server-count", o.serverCount, "The number of proxy server instances, should be 1 unless it is an HA server.")
115+
flags.StringVar(&o.agentNamespace, "agent-namespace", o.agentNamespace, "Expected agent's namespace during agent authentication (used with agent-service-account, authentication-audience, kubeconfig).")
116+
flags.StringVar(&o.agentServiceAccount, "agent-service-account", o.agentServiceAccount, "Expected agent's service account during agent authentication (used with agent-namespace, authentication-audience, kubeconfig).")
117+
flags.StringVar(&o.kubeconfigPath, "kubeconfig", o.kubeconfigPath, "absolute path to the kubeconfig file (used with agent-namespace, agent-service-account, authentication-audience).")
118+
flags.StringVar(&o.authenticationAudience, "authentication-audience", o.authenticationAudience, "Expected agent's token authentication audience (used with agent-namespace, agent-service-account, kubeconfig).")
106119
return flags
107120
}
108121

@@ -120,6 +133,10 @@ func (o *ProxyRunOptions) Print() {
120133
klog.Warningf("Admin port set to %d.\n", o.adminPort)
121134
klog.Warningf("ServerID set to %s.\n", o.serverID)
122135
klog.Warningf("ServerCount set to %d.\n", o.serverCount)
136+
klog.Warningf("AgentNamespace set to %q.\n", o.agentNamespace)
137+
klog.Warningf("AgentServiceAccount set to %q.\n", o.agentServiceAccount)
138+
klog.Warningf("AuthenticationAudience set to %q.\n", o.authenticationAudience)
139+
klog.Warningf("KubeconfigPath set to %q.\n", o.kubeconfigPath)
123140
}
124141

125142
func (o *ProxyRunOptions) Validate() error {
@@ -202,24 +219,48 @@ func (o *ProxyRunOptions) Validate() error {
202219
if o.adminPort < 1024 {
203220
return fmt.Errorf("please do not try to use reserved port %d for the admin port", o.adminPort)
204221
}
222+
223+
// validate agent authentication params
224+
// all 4 parametes must be empty or must have value (except kubeconfigPath that might be empty)
225+
if o.agentNamespace != "" || o.agentServiceAccount != "" || o.authenticationAudience != "" || o.kubeconfigPath != "" {
226+
if o.agentNamespace == "" {
227+
return fmt.Errorf("agentNamespace cannot be empty when agent authentication is enabled")
228+
}
229+
if o.agentServiceAccount == "" {
230+
return fmt.Errorf("agentServiceAccount cannot be empty when agent authentication is enabled")
231+
}
232+
if o.authenticationAudience == "" {
233+
return fmt.Errorf("authenticationAudience cannot be empty when agent authentication is enabled")
234+
}
235+
if o.kubeconfigPath != "" {
236+
if _, err := os.Stat(o.kubeconfigPath); os.IsNotExist(err) {
237+
return fmt.Errorf("error checking kubeconfigPath %q, got %v", o.kubeconfigPath, err)
238+
}
239+
}
240+
}
241+
205242
return nil
206243
}
207244

208245
func newProxyRunOptions() *ProxyRunOptions {
209246
o := ProxyRunOptions{
210-
serverCert: "",
211-
serverKey: "",
212-
serverCaCert: "",
213-
clusterCert: "",
214-
clusterKey: "",
215-
clusterCaCert: "",
216-
mode: "grpc",
217-
udsName: "",
218-
serverPort: 8090,
219-
agentPort: 8091,
220-
adminPort: 8092,
221-
serverID: uuid.New().String(),
222-
serverCount: 1,
247+
serverCert: "",
248+
serverKey: "",
249+
serverCaCert: "",
250+
clusterCert: "",
251+
clusterKey: "",
252+
clusterCaCert: "",
253+
mode: "grpc",
254+
udsName: "",
255+
serverPort: 8090,
256+
agentPort: 8091,
257+
adminPort: 8092,
258+
serverID: uuid.New().String(),
259+
serverCount: 1,
260+
agentNamespace: "",
261+
agentServiceAccount: "",
262+
kubeconfigPath: "",
263+
authenticationAudience: "",
223264
}
224265
return &o
225266
}
@@ -247,7 +288,29 @@ func (p *Proxy) run(o *ProxyRunOptions) error {
247288
return fmt.Errorf("failed to validate server options with %v", err)
248289
}
249290
ctx, cancel := context.WithCancel(context.Background())
250-
server := agentserver.NewProxyServer(o.serverID, int(o.serverCount))
291+
defer cancel()
292+
293+
var k8sClient *kubernetes.Clientset
294+
if o.agentNamespace != "" {
295+
config, err := clientcmd.BuildConfigFromFlags("", o.kubeconfigPath)
296+
if err != nil {
297+
return fmt.Errorf("failed to load kubernetes client config: %v", err)
298+
}
299+
300+
k8sClient, err = kubernetes.NewForConfig(config)
301+
if err != nil {
302+
return fmt.Errorf("failed to create kubernetes clientset: %v", err)
303+
}
304+
}
305+
306+
authOpt := &agentserver.AgentTokenAuthenticationOptions{
307+
Enabled: o.agentNamespace != "",
308+
AgentNamespace: o.agentNamespace,
309+
AgentServiceAccount: o.agentServiceAccount,
310+
KubernetesClient: k8sClient,
311+
AuthenticationAudience: o.authenticationAudience,
312+
}
313+
server := agentserver.NewProxyServer(o.serverID, int(o.serverCount), authOpt)
251314

252315
klog.Info("Starting master server for client connections.")
253316
masterStop, err := p.runMasterServer(ctx, o, server)
@@ -274,7 +337,6 @@ func (p *Proxy) run(o *ProxyRunOptions) error {
274337
if masterStop != nil {
275338
masterStop()
276339
}
277-
cancel()
278340

279341
return nil
280342
}

examples/kubernetes/README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,42 @@ KUBIA_IP=$(kubectl get svc kubia -o=jsonpath='{.spec.clusterIP}')
2020
PROXY_IMAGE=$(docker images | grep "proxy-server-" -m1 | awk '{print $1}')
2121
AGENT_IMAGE=$(docker images | grep "proxy-agent-" -m1 | awk '{print $1}')
2222
TEST_CLIENT_IMAGE=$(docker images | grep "proxy-test-client-" -m1 | awk '{print $1}')
23+
SERVER_TOKEN=$(./examples/kubernetes/token_generation.sh 32)
2324
CLUSTER_CERT=<yourdirectory/server.crt>
2425
CLUSTER_KEY=</yourdirectory/server.key>
2526
```
2627

27-
#### GKE specific configuration
28+
#### GCE sample configuration
2829
```bash
2930
CLUSTER_CERT=/etc/srv/kubernetes/pki/apiserver.crt
3031
CLUSTER_KEY=/etc/srv/kubernetes/pki/apiserver.key
3132
```
3233

34+
# Register SERVER_TOKEN in [static-token-file](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#static-token-file)
35+
Append the output of the following line to the [static-token-file](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#static-token-file) and restart **kube-apiserver** on the master
36+
```bash
37+
echo "${SERVER_TOKEN},system:konnectivity-server,uid:system:konnectivity-server"
38+
```
39+
40+
#### GCE sample configuration
41+
1. [static-token-file](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#static-token-file) location is: **/etc/srv/kubernetes/known_tokens.csv**
42+
43+
1. Restart kube-apiserver
44+
```bash
45+
K8S_API_PID=$(sudo crictl ps | grep kube-apiserver | awk '{ print $1; }')
46+
sudo crictl stop ${K8S_API_PID}
47+
```
48+
49+
# Save following config at /etc/srv/kubernetes/konnectivity-server/kubeconfig on master VM
50+
```bash
51+
SERVER_TOKEN=${SERVER_TOKEN} envsubst < examples/kubernetes/kubeconfig
52+
```
53+
54+
# Create a clusterrolebinding allowing proxy-server authenticate proxy-client
55+
```bash
56+
kubectl create clusterrolebinding --user system:konnectivity-server --clusterrole system:auth-delegator system:konnectivity-server
57+
```
58+
3359
# Start **proxy-server** as a [static pod](https://kubernetes.io/docs/tasks/configure-pod-container/static-pod/) with following configuration
3460
```bash
3561
TAG=${TAG} PROXY_IMAGE=${PROXY_IMAGE} CLUSTER_CERT=${CLUSTER_CERT} CLUSTER_KEY=${CLUSTER_KEY} envsubst < examples/kubernetes/konnectivity-server.yaml

examples/kubernetes/konnectivity-agent.yaml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
apiVersion: v1
2+
kind: ServiceAccount
3+
metadata:
4+
name: konnectivity-agent
5+
namespace: kube-system
6+
---
7+
apiVersion: v1
28
kind: Pod
39
metadata:
410
name: konnectivity-agent
5-
namespace: default
11+
namespace: kube-system
612
annotations:
713
scheduler.alpha.kubernetes.io/critical-pod: ''
814
seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
@@ -20,6 +26,7 @@ spec:
2026
"--ca-cert=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
2127
"--proxy-server-host=${CLUSTER_IP}",
2228
"--proxy-server-port=8091",
29+
"--service-account-token-path=/var/run/secrets/tokens/konnectivity-agent-token",
2330
]
2431
livenessProbe:
2532
httpGet:
@@ -33,3 +40,14 @@ spec:
3340
limits:
3441
cpu: 50m
3542
memory: 30Mi
43+
volumeMounts:
44+
- mountPath: /var/run/secrets/tokens
45+
name: konnectivity-agent-token
46+
serviceAccountName: konnectivity-agent
47+
volumes:
48+
- name: konnectivity-agent-token
49+
projected:
50+
sources:
51+
- serviceAccountToken:
52+
path: konnectivity-agent-token
53+
audience: system:konnectivity-server

examples/kubernetes/konnectivity-server.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ spec:
1919
"--log-file=/var/log/konnectivity-server.log",
2020
"--logtostderr=false",
2121
"--log-file-max-size=0",
22-
"--uds-name=/etc/srv/kubernetes/konnectivity/konnectivity-server.socket",
22+
"--uds-name=/etc/srv/kubernetes/konnectivity-server/konnectivity-server.socket",
2323
"--cluster-cert=/etc/srv/kubernetes/pki/apiserver.crt",
2424
"--cluster-key=/etc/srv/kubernetes/pki/apiserver.key",
2525
"--server-port=0",
2626
"--agent-port=8091",
2727
"--admin-port=8092",
28-
"--mode=http-connect"
28+
"--mode=http-connect",
29+
"--agent-namespace=kube-system",
30+
"--agent-service-account=konnectivity-agent",
31+
"--kubeconfig=/etc/srv/kubernetes/konnectivity-server/kubeconfig",
32+
"--authentication-audience=system:konnectivity-server",
2933
]
3034
livenessProbe:
3135
httpGet:
@@ -53,7 +57,7 @@ spec:
5357
mountPath: /etc/srv/kubernetes/pki
5458
readOnly: true
5559
- name: konnectivity-home
56-
mountPath: /etc/srv/kubernetes/konnectivity
60+
mountPath: /etc/srv/kubernetes/konnectivity-server
5761
volumes:
5862
- name: varlogkonnectivityserver
5963
hostPath:
@@ -64,6 +68,5 @@ spec:
6468
path: /etc/srv/kubernetes/pki
6569
- name: konnectivity-home
6670
hostPath:
67-
path: /etc/srv/kubernetes/konnectivity
71+
path: /etc/srv/kubernetes/konnectivity-server
6872
type: DirectoryOrCreate
69-

0 commit comments

Comments
 (0)