Skip to content

Commit e6538b6

Browse files
committed
MINOR: cluster: add endpoint for cert refresh
POST /cluster/certificate can be used to initiate certificate refresh in cluster mode
1 parent d618eb5 commit e6538b6

10 files changed

+600
-3
lines changed

configuration/cluster_sync.go

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import (
3636
log "github.com/sirupsen/logrus"
3737
)
3838

39+
const DataplaneAPIType = "community"
40+
3941
//Node is structure required for connection to cluster
4042
type Node struct {
4143
Address string `json:"address"`
@@ -64,6 +66,9 @@ func (c *ClusterSync) Monitor(cfg *Configuration, cli *client_native.HAProxyClie
6466
c.cli = cli
6567

6668
go c.monitorBootstrapKey()
69+
if c.cfg.Mode.Load() == "cluster" {
70+
go c.monitorCertificateRefresh()
71+
}
6772

6873
c.certFetch = make(chan struct{}, 2)
6974
go c.fetchCert()
@@ -76,6 +81,98 @@ func (c *ClusterSync) Monitor(cfg *Configuration, cli *client_native.HAProxyClie
7681
}
7782
}
7883

84+
func (c *ClusterSync) monitorCertificateRefresh() {
85+
for range c.cfg.Notify.CertificateRefresh.Subscribe("monitorCertificateRefresh") {
86+
log.Info("refreshing certificate")
87+
88+
key := c.cfg.BootstrapKey.Load()
89+
data, err := decodeBootstrapKey(key)
90+
if err != nil {
91+
log.Warning(err)
92+
continue
93+
}
94+
if len(data) != 8 {
95+
log.Warning("bottstrap key in unrecognized format")
96+
continue
97+
}
98+
url := fmt.Sprintf("%s://%s", data[0], data[1])
99+
100+
csr, key, err := generateCSR()
101+
if err != nil {
102+
log.Warning(err)
103+
continue
104+
}
105+
err = ioutil.WriteFile(c.cfg.Cluster.CertificateCSR.Load(), []byte(csr), 0644)
106+
if err != nil {
107+
log.Warning(err)
108+
continue
109+
}
110+
err = c.issueRefreshRequest(url, data[2], data[3], data[4], csr, key)
111+
if err != nil {
112+
log.Warning(err)
113+
continue
114+
}
115+
}
116+
}
117+
118+
func (c *ClusterSync) issueRefreshRequest(url, port, basePath string, nodesPath string, csr, key string) error {
119+
url = fmt.Sprintf("%s:%s%s/%s/%s", url, port, basePath, nodesPath, c.cfg.Cluster.ID.Load())
120+
nodeData := Node{
121+
ID: c.cfg.Cluster.ID.Load(),
122+
Address: c.cfg.Server.Host,
123+
Certificate: csr,
124+
Status: cfg.Status.Load(),
125+
Type: DataplaneAPIType,
126+
}
127+
bytesRepresentation, _ := json.Marshal(nodeData)
128+
129+
req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(bytesRepresentation))
130+
if err != nil {
131+
return fmt.Errorf("error creating new POST request for cluster comunication")
132+
}
133+
req.Header.Add("X-Node-Key", c.cfg.Cluster.Token.Load())
134+
req.Header.Add("Content-Type", "application/json")
135+
log.Infof("Refreshing certificate %s", url)
136+
httpClient := createHTTPClient()
137+
resp, err := httpClient.Do(req)
138+
if err != nil {
139+
return err
140+
}
141+
defer resp.Body.Close()
142+
143+
body, err := ioutil.ReadAll(resp.Body)
144+
if err != nil {
145+
return err
146+
}
147+
if resp.StatusCode != 202 {
148+
return fmt.Errorf("status code not proper [%d] %s", resp.StatusCode, string(body))
149+
}
150+
var responseData Node
151+
err = json.Unmarshal(body, &responseData)
152+
if err != nil {
153+
return err
154+
}
155+
log.Infof("Cluster re joined, status: %s", responseData.Status)
156+
err = ioutil.WriteFile(c.cfg.Cluster.CertificatePath.Load(), []byte(responseData.Certificate), 0644)
157+
if err != nil {
158+
log.Warning(err)
159+
return err
160+
}
161+
err = ioutil.WriteFile(c.cfg.Cluster.CertificateKeyPath.Load(), []byte(key), 0644)
162+
if err != nil {
163+
log.Warning(err)
164+
return err
165+
}
166+
c.cfg.Cluster.Token.Store(resp.Header.Get("X-Node-Key"))
167+
err = c.cfg.Save()
168+
if err != nil {
169+
log.Warning(err)
170+
return err
171+
}
172+
c.cfg.Notify.Reload.Notify()
173+
return nil
174+
}
175+
79176
func (c *ClusterSync) monitorBootstrapKey() {
80177
for range c.cfg.Notify.BootstrapKeyChanged.Subscribe("monitorBootstrapKey") {
81178
key := c.cfg.BootstrapKey.Load()
@@ -173,7 +270,7 @@ func (c *ClusterSync) issueJoinRequest(url, port, basePath string, nodesPath str
173270
Name: c.cfg.Name.Load(),
174271
Port: int64(serverCfg.Port),
175272
Status: "waiting_approval",
176-
Type: "community",
273+
Type: DataplaneAPIType,
177274
}
178275
bytesRepresentation, _ := json.Marshal(nodeData)
179276

configuration/configuration.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ type ServerConfiguration struct {
9595

9696
type NotifyConfiguration struct {
9797
BootstrapKeyChanged *ChanNotify `yaml:"-"`
98+
CertificateRefresh *ChanNotify `yaml:"-"`
9899
Reload *ChanNotify `yaml:"-"`
99100
Shutdown *ChanNotify `yaml:"-"`
100101
}
@@ -117,6 +118,7 @@ func Get() *Configuration {
117118
cfg = &Configuration{}
118119
cfg.initSignalHandler()
119120
cfg.Notify.BootstrapKeyChanged = NewChanNotify()
121+
cfg.Notify.CertificateRefresh = NewChanNotify()
120122
cfg.Notify.Reload = NewChanNotify()
121123
cfg.Notify.Shutdown = NewChanNotify()
122124
}
@@ -134,6 +136,7 @@ func (c *Configuration) BotstrapKeyChanged(bootstrapKey string) {
134136

135137
func (c *Configuration) UnSubscribeAll() {
136138
c.Notify.BootstrapKeyChanged.UnSubscribeAll()
139+
c.Notify.CertificateRefresh.UnSubscribeAll()
137140
c.Notify.Reload.UnSubscribeAll()
138141
c.Notify.Shutdown.UnSubscribeAll()
139142
}

configure_data_plane.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ func configureAPI(api *operations.DataPlaneAPI) http.Handler {
409409
// setup cluster handlers
410410
api.DiscoveryGetClusterHandler = &handlers.GetClusterHandlerImpl{Config: cfg}
411411
api.ClusterPostClusterHandler = &handlers.CreateClusterHandlerImpl{Config: cfg}
412+
api.ClusterInitiateCertificateRefreshHandler = &handlers.ClusterInitiateCertificateRefreshHandlerImpl{Config: cfg}
412413

413414
clusterSync := dataplaneapi_config.ClusterSync{ReloadAgent: ra}
414415
go clusterSync.Monitor(cfg, client)

embedded_spec.go

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

handlers/cluster.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,30 @@ import (
2525
"github.com/haproxytech/models"
2626
)
2727

28-
//CreateClusterHandlerImpl implementation of the CreateClusterHandler interface using client-native client
28+
//CreateClusterHandlerImpl implementation of the CreateClusterHandler interface
2929
type CreateClusterHandlerImpl struct {
3030
Config *configuration.Configuration
3131
}
3232

33-
//GetClusterHandlerImpl implementation of the GetClusterHandler interface using client-native client
33+
//GetClusterHandlerImpl implementation of the GetClusterHandler interface
3434
type GetClusterHandlerImpl struct {
3535
Config *configuration.Configuration
3636
}
3737

38+
//ClusterInitiateCertificateRefreshHandlerImpl implementation of the ClusterInitiateCertificateRefreshHandler interface
39+
type ClusterInitiateCertificateRefreshHandlerImpl struct {
40+
Config *configuration.Configuration
41+
}
42+
3843
//Handle executing the request and returning a response
44+
func (h *ClusterInitiateCertificateRefreshHandlerImpl) Handle(params cluster.InitiateCertificateRefreshParams, principal interface{}) middleware.Responder {
45+
if h.Config.Mode.Load() != "cluster" {
46+
return cluster.NewInitiateCertificateRefreshForbidden()
47+
}
48+
h.Config.Notify.CertificateRefresh.Notify()
49+
return cluster.NewInitiateCertificateRefreshOK()
50+
}
51+
3952
func (h *CreateClusterHandlerImpl) Handle(params cluster.PostClusterParams, principal interface{}) middleware.Responder {
4053
key := h.Config.BootstrapKey.Load()
4154
if params.Data.BootstrapKey != "" && key != params.Data.BootstrapKey {

operations/cluster/initiate_certificate_refresh.go

Lines changed: 88 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)