forked from smallstep/certificates
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add validation agent functionality
- Loading branch information
1 parent
e5f3309
commit f86ce26
Showing
12 changed files
with
473 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package mqtt | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"net/url" | ||
"time" | ||
|
||
mqtt "github.com/eclipse/paho.mqtt.golang" | ||
"github.com/sirupsen/logrus" | ||
"github.com/smallstep/certificates/acme" | ||
"github.com/smallstep/certificates/acme/validation" | ||
) | ||
|
||
var clock acme.Clock | ||
|
||
func Connect(acmeDB acme.DB, host, user, password, organization string) (validation.MqttClient, error) { | ||
opts := mqtt.NewClientOptions() | ||
opts.SetOrderMatters(false) // Allow out of order messages (use this option unless in order delivery is essential) | ||
opts.ConnectTimeout = time.Second // Minimal delays on connect | ||
opts.WriteTimeout = time.Second // Minimal delays on writes | ||
opts.KeepAlive = 10 // Keepalive every 10 seconds so we quickly detect network outages | ||
opts.PingTimeout = time.Second // local broker so response should be quick | ||
opts.ConnectRetry = true | ||
opts.AutoReconnect = true | ||
opts.ClientID = "acme" | ||
opts.Username = user | ||
opts.Password = password | ||
opts.AddBroker(fmt.Sprintf("ssl://%s:8883", host)) | ||
logrus.Infof("connecting to mqtt broker") | ||
// Log events | ||
opts.OnConnectionLost = func(cl mqtt.Client, err error) { | ||
logrus.Println("mqtt connection lost") | ||
} | ||
opts.OnConnect = func(mqtt.Client) { | ||
logrus.Println("mqtt connection established") | ||
} | ||
opts.OnReconnecting = func(mqtt.Client, *mqtt.ClientOptions) { | ||
logrus.Println("mqtt attempting to reconnect") | ||
} | ||
|
||
client := mqtt.NewClient(opts) | ||
|
||
if token := client.Connect(); token.WaitTimeout(30*time.Second) && token.Error() != nil { | ||
logrus.Warn(token.Error()) | ||
return nil, token.Error() | ||
} | ||
|
||
go func() { | ||
client.Subscribe(fmt.Sprintf("%s/data", organization), 1, func(client mqtt.Client, msg mqtt.Message) { | ||
logrus.Printf("Received message on topic: %s\nMessage: %s\n", msg.Topic(), msg.Payload()) | ||
ctx := context.Background() | ||
data := msg.Payload() | ||
var payload validation.ValidationResponse | ||
err := json.Unmarshal(data, &payload) | ||
if err != nil { | ||
logrus.Errorf("error unmarshalling payload: %v", err) | ||
return | ||
} | ||
|
||
ch, err := acmeDB.GetChallenge(ctx, payload.Challenge, payload.Authz) | ||
if err != nil { | ||
logrus.Errorf("error getting challenge: %v", err) | ||
return | ||
} | ||
|
||
acc, err := acmeDB.GetAccount(ctx, ch.AccountID) | ||
if err != nil { | ||
logrus.Errorf("error getting account: %v", err) | ||
return | ||
} | ||
expected, err := acme.KeyAuthorization(ch.Token, acc.Key) | ||
|
||
if payload.Content != expected || err != nil { | ||
logrus.Errorf("invalid key authorization: %v", err) | ||
return | ||
} | ||
u := &url.URL{Scheme: "http", Host: ch.Value, Path: fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Token)} | ||
logrus.Infof("challenge %s validated using mqtt", u.String()) | ||
|
||
if ch.Status != acme.StatusPending && ch.Status != acme.StatusValid { | ||
return | ||
} | ||
|
||
ch.Status = acme.StatusValid | ||
ch.Error = nil | ||
ch.ValidatedAt = clock.Now().Format(time.RFC3339) | ||
|
||
if err = acmeDB.UpdateChallenge(ctx, ch); err != nil { | ||
logrus.Errorf("error updating challenge: %v", err) | ||
} else { | ||
logrus.Infof("challenge %s updated to valid", u.String()) | ||
} | ||
|
||
}) | ||
}() | ||
connection := validation.BrokerConnection{Client: client, Organization: organization} | ||
return connection, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package validation | ||
|
||
import ( | ||
"context" | ||
|
||
mqtt "github.com/eclipse/paho.mqtt.golang" | ||
) | ||
|
||
type ValidationResponse struct { | ||
Authz string `json:"authz"` | ||
Challenge string `json:"challenge"` | ||
Content string `json:"content"` | ||
} | ||
|
||
type ValidationRequest struct { | ||
Authz string `json:"authz"` | ||
Challenge string `json:"challenge"` | ||
Target string `json:"target"` | ||
} | ||
|
||
type validationKey struct{} | ||
|
||
type MqttClient interface { | ||
GetClient() mqtt.Client | ||
GetOrganization() string | ||
} | ||
|
||
type BrokerConnection struct { | ||
Client mqtt.Client | ||
Organization string | ||
} | ||
|
||
func (b BrokerConnection) GetClient() mqtt.Client { | ||
return b.Client | ||
} | ||
|
||
func (b BrokerConnection) GetOrganization() string { | ||
return b.Organization | ||
} | ||
|
||
// NewContext adds the given validation client to the context. | ||
func NewContext(ctx context.Context, a MqttClient) context.Context { | ||
return context.WithValue(ctx, validationKey{}, a) | ||
} | ||
|
||
// FromContext returns the validation client from the given context. | ||
func FromContext(ctx context.Context) (a MqttClient, ok bool) { | ||
a, ok = ctx.Value(validationKey{}).(MqttClient) | ||
return | ||
} | ||
|
||
// MustFromContext returns the validation client from the given context. It will | ||
// panic if no validation client is not in the context. | ||
func MustFromContext(ctx context.Context) MqttClient { | ||
if a, ok := FromContext(ctx); !ok { | ||
panic("validation client is not in the context") | ||
} else { | ||
return a | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.