Skip to content

Commit 44fc4f8

Browse files
committed
wip gateway-agent
1 parent df178a3 commit 44fc4f8

File tree

2 files changed

+151
-2
lines changed

2 files changed

+151
-2
lines changed

apiserver/api/api.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ type api struct {
1313
db database.APIServerDB
1414
}
1515

16+
type Peer struct {
17+
PublicKey string
18+
IP string
19+
}
20+
1621
// TODO(jhrv): do actual filtering of the clients.
1722
// TODO(jhrv): keep cache of gateway access group members to remove AAD runtime dependency
1823
// gatewayConfig returns the clients for the gateway that has the group membership required
@@ -26,8 +31,13 @@ func (a *api) gatewayConfig(w http.ResponseWriter, r *http.Request) {
2631
return
2732
}
2833

34+
peers := make([]Peer, 0)
35+
for _, client := range clients {
36+
peers = append(peers, Peer{PublicKey: client.PublicKey, IP: client.IP})
37+
}
38+
2939
w.WriteHeader(http.StatusOK)
30-
json.NewEncoder(w).Encode(clients)
40+
json.NewEncoder(w).Encode(peers)
3141
}
3242

3343
func (a *api) clients(w http.ResponseWriter, r *http.Request) {
@@ -65,7 +75,7 @@ func (a *api) updateHealth(w http.ResponseWriter, r *http.Request) {
6575
if err := a.db.UpdateClientStatus(healthUpdates); err != nil {
6676
w.WriteHeader(http.StatusInternalServerError)
6777
log.Error(err)
68-
w.Write([]byte("unable to persist client statuses"))
78+
w.Write([]byte("unable to persist client statuses\n"))
6979
return
7080
}
7181
}

cmd/gateway-agent/main.go

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"path/filepath"
9+
"time"
10+
11+
"github.com/nais/device/apiserver/api"
12+
log "github.com/sirupsen/logrus"
13+
flag "github.com/spf13/pflag"
14+
)
15+
16+
var (
17+
cfg = DefaultConfig()
18+
)
19+
20+
func init() {
21+
log.SetFormatter(&log.JSONFormatter{})
22+
flag.StringVar(&cfg.Apiserver, "apiserver", cfg.Apiserver, "hostname to apiserver")
23+
flag.StringVar(&cfg.Name, "name", cfg.Name, "hostname to apiserver")
24+
flag.StringVar(&cfg.PublicKey, "public-key", cfg.PublicKey, "path to wireguard public key")
25+
flag.StringVar(&cfg.PrivateKey, "private-key", cfg.PrivateKey, "path to wireguard private key")
26+
flag.StringVar(&cfg.TunnelInterfaceName, "interface", cfg.TunnelInterfaceName, "name of tunnel interface")
27+
flag.StringVar(&cfg.TunnelConfigDir, "tunnel-config-dir", cfg.TunnelConfigDir, "path to tunnel interface config directory")
28+
29+
flag.Parse()
30+
}
31+
32+
// Gateway agent ensures desired configuration as defined by the apiserver
33+
// is synchronized and enforced by the local wireguard process on the gateway.
34+
//
35+
// Prerequisites:
36+
// - controlplane tunnel is set up/apiserver is reachable at `Config.Apiserver`
37+
//
38+
// Prereqs for MVP (at least):
39+
//
40+
// - wireguard keypair is generated and provided as `Config.{Public,Private}Key`
41+
// - gateway is registered
42+
// - tunnel ip is configured on wireguard interface for dataplane (see below)
43+
//
44+
//# ip link add dev wg0 type wireguard
45+
//# ip addr add <tunnelip> wg0
46+
//# ip link set wg0 up
47+
func main() {
48+
log.Info("starting gateway-agent")
49+
log.Infof("with config:\n%+v", cfg)
50+
51+
privateKey, err := readPrivateKey()
52+
53+
if err != nil {
54+
log.Fatalf("reading private key: %s", err)
55+
}
56+
57+
for range time.NewTicker(10 * time.Second).C {
58+
log.Infof("getting config")
59+
apiserver := fmt.Sprintf("%s/gateways/%s", cfg.Apiserver, cfg.Name)
60+
peers, err := getPeers(apiserver)
61+
if err != nil {
62+
log.Error(err)
63+
// inc metric
64+
}
65+
66+
fmt.Printf("%+v\n", peers)
67+
68+
if err := configureWireguard(peers, privateKey); err != nil {
69+
log.Error(err)
70+
// inc metric
71+
}
72+
}
73+
}
74+
75+
func readPrivateKey() (string, error) {
76+
privateKeyPath := filepath.Join(cfg.TunnelConfigDir, cfg.TunnelInterfaceName+".conf")
77+
b, err := ioutil.ReadFile(privateKeyPath)
78+
return string(b), err
79+
}
80+
81+
func configureWireguard(peers []api.Peer, privateKey string) error {
82+
// transform peers into wg0.conf
83+
wg0conf := generateWGConfig(peers, privateKey)
84+
fmt.Println(string(wg0conf))
85+
ioutil.WriteFile("/etc/wireguard/wg0.conf", wg0conf, 0600)
86+
87+
//exec.Command("wg", "syncconf", "wg0", "/etc/wireguard/wg0.conf")
88+
89+
return nil
90+
}
91+
92+
func generateWGConfig(peers []api.Peer, privateKey string) []byte {
93+
wgConfig := "[Interface]\n"
94+
wgConfig += fmt.Sprintf("PrivateKey = %s\n", privateKey)
95+
for _, peer := range peers {
96+
wgConfig += "[Peer]\n"
97+
wgConfig += fmt.Sprintf("PublicKey = %s\n", peer.PublicKey)
98+
wgConfig += fmt.Sprintf("AllowedIPs = %s\n\n", peer.IP)
99+
}
100+
101+
return []byte(wgConfig)
102+
}
103+
104+
func getPeers(apiserverURL string) (peers []api.Peer, err error) {
105+
resp, err := http.Get(apiserverURL)
106+
if err != nil {
107+
return nil, fmt.Errorf("getting peer config from apiserver: %s", err)
108+
}
109+
110+
defer resp.Body.Close()
111+
112+
err = json.NewDecoder(resp.Body).Decode(&peers)
113+
114+
if err != nil {
115+
return nil, fmt.Errorf("unmarshal json from apiserver: %s", err)
116+
}
117+
118+
return
119+
}
120+
121+
type Config struct {
122+
Apiserver string
123+
Name string
124+
PublicKey string
125+
PrivateKey string
126+
TunnelIP string
127+
TunnelConfigDir string
128+
TunnelInterfaceName string
129+
}
130+
131+
func DefaultConfig() Config {
132+
return Config{
133+
Apiserver: "http://apiserver.device.nais.io",
134+
PublicKey: "/etc/wireguard/public.key",
135+
PrivateKey: "/etc/wireguard/private.key",
136+
TunnelInterfaceName: "wgdata",
137+
TunnelConfigDir: "/etc/wireguard/",
138+
}
139+
}

0 commit comments

Comments
 (0)