tavern/start/service.go

158 lines
3.8 KiB
Go

package start
import (
"bytes"
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"runtime"
"strings"
"github.com/getsentry/sentry-go"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"github.com/ngerakines/tavern/config"
"github.com/ngerakines/tavern/errors"
"github.com/ngerakines/tavern/g"
"github.com/ngerakines/tavern/storage"
)
var InitServiceCommand = cli.Command{
Name: "init-service",
Usage: "Initialize the service",
Flags: []cli.Flag{
&config.DomainFlag,
&config.DatabaseFlag,
&cli.StringFlag{
Name: "pem",
Usage: "The path to the private key PEM file.",
Required: true,
},
},
Action: initServiceCommandAction,
}
func initServiceCommandAction(cliCtx *cli.Context) error {
logger, err := config.Logger(cliCtx)
if err != nil {
return err
}
domain := cliCtx.String("domain")
siteBase := fmt.Sprintf("https://%s", domain)
logger.Info("Starting",
zap.String("command", cliCtx.Command.Name),
zap.String("GOOS", runtime.GOOS),
zap.String("site", siteBase),
zap.String("env", cliCtx.String("environment")))
sentryConfig, err := config.NewSentryConfig(cliCtx)
if err != nil {
return err
}
if sentryConfig.Enabled {
err = sentry.Init(sentry.ClientOptions{
Dsn: sentryConfig.Key,
Environment: cliCtx.String("environment"),
Release: fmt.Sprintf("%s-%s", g.Release, g.GitCommit),
})
if err != nil {
return err
}
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetTags(map[string]string{"container": "server"})
})
defer sentry.Recover()
}
db, dbClose, err := config.DB(cliCtx, logger)
if err != nil {
return err
}
defer dbClose()
actorID := fmt.Sprintf("https://%s/server", domain)
actor := storage.EmptyPayload()
actor["@context"] = "https://www.w3.org/ns/activitystreams"
actor["id"] = actorID
actor["type"] = "Service"
actor["inbox"] = fmt.Sprintf("%s/inbox", actorID)
actor["outbox"] = fmt.Sprintf("%s/outbox", actorID)
actor["name"] = domain
actor["summary"] = domain
actor["preferredUsername"] = domain
actor["url"] = fmt.Sprintf("https://%s/", domain)
ctx := context.Background()
pemBytes, err := ioutil.ReadFile(cliCtx.String("pem"))
if err != nil {
return err
}
block, _ := pem.Decode(pemBytes)
keyID, ok := block.Headers["id"]
if !ok {
return fmt.Errorf("missing header: id")
}
if len(keyID) == 0 || !strings.HasPrefix(keyID, fmt.Sprintf("https://%s/", domain)) {
return fmt.Errorf("invalid key id: %s", keyID)
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf("unable to parse private key bytes: %w", err)
}
publicKeyBytes, err := x509.MarshalPKIXPublicKey(privateKey.Public())
if err != nil {
return fmt.Errorf("unable to marshal public key: %w", err)
}
var publicKeyBuffer bytes.Buffer
if err = pem.Encode(&publicKeyBuffer, &pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}); err != nil {
return err
}
publicKey := string(publicKeyBuffer.Bytes())
txErr := storage.TransactionalStorage(ctx, storage.DefaultStorage(storage.LoggingSQLDriver{Driver: db, Logger: logger}), func(s storage.Storage) error {
keyPayload := storage.EmptyPayload()
keyPayload["id"] = keyID
keyPayload["owner"] = actorID
keyPayload["publicKeyPem"] = publicKey
actor["publicKey"] = keyPayload
err = s.CreateActor(ctx, actorID, actor)
if err != nil {
return err
}
actorRowID, err := s.ActorRowIDForActorID(ctx, actorID)
if err != nil {
return err
}
err = s.RecordActorKey(ctx, actorRowID, keyID, publicKey)
if err != nil {
return err
}
if err = s.RecordActorAlias(ctx, actorRowID, actorID, storage.ActorAliasSelf); err != nil {
return err
}
return nil
})
if txErr != nil {
logger.Error("error creating service", zap.Error(err), zap.Strings("error_chain", errors.ErrorChain(err)))
}
return txErr
}