chore: introduce server key

This commit is contained in:
Johan Siebens
2022-05-18 11:12:39 +02:00
parent b1974d7f83
commit a804aea79b
7 changed files with 164 additions and 65 deletions
+23 -24
View File
@@ -3,12 +3,11 @@ package config
import ( import (
"fmt" "fmt"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/jsiebens/ionscale/internal/util" "github.com/jsiebens/ionscale/internal/key"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io/ioutil" "io/ioutil"
"strings" "strings"
"tailscale.com/types/key"
) )
func LoadConfig(path string) (*Config, error) { func LoadConfig(path string) (*Config, error) {
@@ -83,43 +82,43 @@ func defaultConfig() *Config {
} }
type ServerKeys struct { type ServerKeys struct {
SystemAdminKey key.MachinePrivate SystemAdminKey key.ServerPrivate
} }
type Config struct { type Config struct {
HttpListenAddr string `yaml:"http_listen_addr"` HttpListenAddr string `yaml:"http_listen_addr,omitempty"`
HttpsListenAddr string `yaml:"https_listen_addr"` HttpsListenAddr string `yaml:"https_listen_addr,omitempty"`
MetricsListenAddr string `yaml:"metrics_listen_addr"` MetricsListenAddr string `yaml:"metrics_listen_addr,omitempty"`
ServerUrl string `yaml:"server_url"` ServerUrl string `yaml:"server_url,omitempty"`
Tls Tls `yaml:"tls"` Tls Tls `yaml:"tls,omitempty"`
Logging Logging `yaml:"logging"` Logging Logging `yaml:"logging,omitempty"`
Keys Keys `yaml:"keys"` Keys Keys `yaml:"keys,omitempty"`
Database Database `yaml:"database"` Database Database `yaml:"database,omitempty"`
} }
type Tls struct { type Tls struct {
Disable bool `yaml:"disable"` Disable bool `yaml:"disable"`
CertFile string `yaml:"cert_file"` CertFile string `yaml:"cert_file,omitempty"`
KeyFile string `yaml:"key_file"` KeyFile string `yaml:"key_file,omitempty"`
CertMagicDomain string `yaml:"cert_magic_domain"` CertMagicDomain string `yaml:"cert_magic_domain,omitempty"`
CertMagicEmail string `yaml:"cert_magic_email"` CertMagicEmail string `yaml:"cert_magic_email,omitempty"`
CertMagicCA string `yaml:"cert_magic_ca"` CertMagicCA string `yaml:"cert_magic_ca,omitempty"`
CertMagicStoragePath string `yaml:"cert_magic_storage_path"` CertMagicStoragePath string `yaml:"cert_magic_storage_path,omitempty"`
} }
type Logging struct { type Logging struct {
Level string `yaml:"level"` Level string `yaml:"level,omitempty"`
Format string `yaml:"format"` Format string `yaml:"format,omitempty"`
File string `yaml:"file"` File string `yaml:"file,omitempty"`
} }
type Database struct { type Database struct {
Url string `yaml:"url"` Url string `yaml:"url,omitempty"`
} }
type Keys struct { type Keys struct {
SystemAdminKey string `yaml:"system_admin_key"` SystemAdminKey string `yaml:"system_admin_key,omitempty"`
EncryptionKey string `yaml:"encryption_key"` EncryptionKey string `yaml:"encryption_key,omitempty"`
} }
func (c *Config) CreateUrl(format string, a ...interface{}) string { func (c *Config) CreateUrl(format string, a ...interface{}) string {
@@ -128,7 +127,7 @@ func (c *Config) CreateUrl(format string, a ...interface{}) string {
} }
func (c *Config) ReadServerKeys() (*ServerKeys, error) { func (c *Config) ReadServerKeys() (*ServerKeys, error) {
systemAdminKey, err := util.ParseMachinePrivateKey(c.Keys.SystemAdminKey) systemAdminKey, err := key.ParsePrivateKey(c.Keys.SystemAdminKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("error reading system admin key: %v", err) return nil, fmt.Errorf("error reading system admin key: %v", err)
} }
+3 -3
View File
@@ -8,7 +8,7 @@ import (
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"net/http" "net/http"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/key" tskey "tailscale.com/types/key"
"time" "time"
"github.com/jsiebens/ionscale/internal/config" "github.com/jsiebens/ionscale/internal/config"
@@ -106,8 +106,8 @@ func initializeControlKeys(repository domain.Repository) error {
} }
keys = &domain.ControlKeys{ keys = &domain.ControlKeys{
ControlKey: key.NewMachine(), ControlKey: tskey.NewMachine(),
LegacyControlKey: key.NewMachine(), LegacyControlKey: tskey.NewMachine(),
} }
return repository.SetControlKeys(ctx, keys) return repository.SetControlKeys(ctx, keys)
+119
View File
@@ -0,0 +1,119 @@
package key
import (
crand "crypto/rand"
"crypto/subtle"
"encoding/hex"
"fmt"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/nacl/box"
"io"
)
func NewServerKey() ServerPrivate {
_, key, err := box.GenerateKey(crand.Reader)
if err != nil {
panic(fmt.Sprintf("unable create new key: %v", err))
}
return ServerPrivate{k: *key}
}
func ParsePrivateKey(key string) (*ServerPrivate, error) {
k := new([32]byte)
err := parseHex(k[:], key)
if err != nil {
return nil, err
}
return &ServerPrivate{k: *k}, nil
}
func ParsePublicKey(key string) (*ServerPublic, error) {
k := new([32]byte)
err := parseHex(k[:], key)
if err != nil {
return nil, err
}
return &ServerPublic{k: *k}, nil
}
func parseHex(out []byte, v string) error {
in := []byte(v)
if want := len(out) * 2; len(in) != want {
return fmt.Errorf("key hex has the wrong size, got %d want %d", len(in), want)
}
_, err := hex.Decode(out[:], in)
if err != nil {
return err
}
return nil
}
type ServerPrivate struct {
k [32]byte
}
type ServerPublic struct {
k [32]byte
}
func (k ServerPrivate) Public() ServerPublic {
var ret ServerPublic
curve25519.ScalarBaseMult(&ret.k, &k.k)
return ret
}
func (k ServerPrivate) Equal(other ServerPrivate) bool {
return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1
}
func (k ServerPrivate) IsZero() bool {
return k.Equal(ServerPrivate{})
}
func (k ServerPrivate) Seal(cleartext []byte) (ciphertext []byte) {
if k.IsZero() {
panic("can't seal with zero keys")
}
var nonce [24]byte
rand(nonce[:])
p := k.Public()
return box.Seal(nonce[:], cleartext, &nonce, &p.k, &k.k)
}
func (k ServerPrivate) Open(ciphertext []byte) (cleartext []byte, ok bool) {
if k.IsZero() {
panic("can't open with zero keys")
}
if len(ciphertext) < 24 {
return nil, false
}
var nonce [24]byte
copy(nonce[:], ciphertext)
p := k.Public()
return box.Open(nil, ciphertext[len(nonce):], &nonce, &p.k, &k.k)
}
func (k ServerPrivate) String() string {
return hex.EncodeToString(k.k[:])
}
func (k ServerPublic) Equal(other ServerPublic) bool {
return subtle.ConstantTimeCompare(k.k[:], other.k[:]) == 1
}
func (k ServerPublic) IsZero() bool {
return k.Equal(ServerPublic{})
}
func (k ServerPublic) String() string {
return hex.EncodeToString(k.k[:])
}
func rand(b []byte) {
if _, err := io.ReadFull(crand.Reader, b[:]); err != nil {
panic(fmt.Sprintf("unable to read random bytes from OS: %v", err))
}
}
+2 -2
View File
@@ -6,16 +6,16 @@ import (
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
"github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/service" "github.com/jsiebens/ionscale/internal/service"
"google.golang.org/grpc" "google.golang.org/grpc"
"tailscale.com/types/key"
) )
func init() { func init() {
grpc_prometheus.EnableHandlingTimeHistogram() grpc_prometheus.EnableHandlingTimeHistogram()
} }
func NewGrpcServer(logger hclog.Logger, systemAdminKey key.MachinePrivate) *grpc.Server { func NewGrpcServer(logger hclog.Logger, systemAdminKey key.ServerPrivate) *grpc.Server {
return grpc.NewServer( return grpc.NewServer(
middleware.WithUnaryServerChain( middleware.WithUnaryServerChain(
logging.UnaryServerInterceptor( logging.UnaryServerInterceptor(
+3 -3
View File
@@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/jsiebens/ionscale/internal/broker" "github.com/jsiebens/ionscale/internal/broker"
"github.com/jsiebens/ionscale/internal/domain" "github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/token" "github.com/jsiebens/ionscale/internal/token"
"github.com/jsiebens/ionscale/internal/version" "github.com/jsiebens/ionscale/internal/version"
"github.com/jsiebens/ionscale/pkg/gen/api" "github.com/jsiebens/ionscale/pkg/gen/api"
@@ -12,7 +13,6 @@ import (
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"strings" "strings"
"tailscale.com/types/key"
) )
var ( var (
@@ -44,7 +44,7 @@ func (s *Service) GetVersion(ctx context.Context, req *api.GetVersionRequest) (*
}, nil }, nil
} }
func UnaryServerTokenAuth(systemAdminKey key.MachinePrivate) func(context.Context, interface{}, *grpc.UnaryServerInfo, grpc.UnaryHandler) (interface{}, error) { func UnaryServerTokenAuth(systemAdminKey key.ServerPrivate) func(context.Context, interface{}, *grpc.UnaryServerInfo, grpc.UnaryHandler) (interface{}, error) {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if strings.HasSuffix(info.FullMethod, "/GetVersion") { if strings.HasSuffix(info.FullMethod, "/GetVersion") {
@@ -68,7 +68,7 @@ func UnaryServerTokenAuth(systemAdminKey key.MachinePrivate) func(context.Contex
} }
} }
func validateAuthorizationToken(systemAdminKey key.MachinePrivate, authorization []string) bool { func validateAuthorizationToken(systemAdminKey key.ServerPrivate, authorization []string) bool {
if len(authorization) != 1 { if len(authorization) != 1 {
return false return false
} }
+11 -11
View File
@@ -5,10 +5,10 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/util" "github.com/jsiebens/ionscale/internal/util"
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
"strings" "strings"
"tailscale.com/types/key"
"time" "time"
) )
@@ -29,7 +29,7 @@ func IsSystemAdminToken(token string) bool {
return strings.HasPrefix(token, systemAdminTokenPrefix) return strings.HasPrefix(token, systemAdminTokenPrefix)
} }
func ParseSystemAdminToken(privKey key.MachinePrivate, versionedToken string) (*Info, error) { func ParseSystemAdminToken(privKey key.ServerPrivate, versionedToken string) (*Info, error) {
versionedToken = strings.TrimSpace(versionedToken) versionedToken = strings.TrimSpace(versionedToken)
if versionedToken == "" { if versionedToken == "" {
return nil, errors.New("empty token") return nil, errors.New("empty token")
@@ -50,7 +50,7 @@ func ParseSystemAdminToken(privKey key.MachinePrivate, versionedToken string) (*
info := new(Info) info := new(Info)
if err := unmarshal(marshaledBlob, info, privKey.Public(), privKey); err != nil { if err := unmarshal(marshaledBlob, info, privKey); err != nil {
return nil, fmt.Errorf("error unmarshaling token info: %w", err) return nil, fmt.Errorf("error unmarshaling token info: %w", err)
} }
@@ -77,7 +77,7 @@ func ParseSystemAdminToken(privKey key.MachinePrivate, versionedToken string) (*
return info, nil return info, nil
} }
func GenerateSystemAdminToken(privKey key.MachinePrivate) (string, error) { func GenerateSystemAdminToken(privKey key.ServerPrivate) (string, error) {
b, err := util.RandomBytes(nonceLength) b, err := util.RandomBytes(nonceLength)
if err != nil { if err != nil {
return "", fmt.Errorf("error generating random bytes for token nonce: %w", err) return "", fmt.Errorf("error generating random bytes for token nonce: %w", err)
@@ -87,11 +87,11 @@ func GenerateSystemAdminToken(privKey key.MachinePrivate) (string, error) {
CreationTime: time.Now(), CreationTime: time.Now(),
} }
return formatToken(privKey.Public(), privKey, systemAdminTokenPrefix, info) return formatToken(privKey, systemAdminTokenPrefix, info)
} }
func formatToken(pubKey key.MachinePublic, privKey key.MachinePrivate, prefix string, v interface{}) (string, error) { func formatToken(privKey key.ServerPrivate, prefix string, v interface{}) (string, error) {
blobInfo, err := marshal(v, pubKey, privKey) blobInfo, err := marshal(v, privKey)
if err != nil { if err != nil {
return "", fmt.Errorf("error encrypting info: %w", err) return "", fmt.Errorf("error encrypting info: %w", err)
} }
@@ -101,17 +101,17 @@ func formatToken(pubKey key.MachinePublic, privKey key.MachinePrivate, prefix st
return fmt.Sprintf("%s%s", prefix, encodedMarshaledBlob), nil return fmt.Sprintf("%s%s", prefix, encodedMarshaledBlob), nil
} }
func marshal(v interface{}, pubKey key.MachinePublic, privKey key.MachinePrivate) ([]byte, error) { func marshal(v interface{}, privKey key.ServerPrivate) ([]byte, error) {
b, err := json.Marshal(v) b, err := json.Marshal(v)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return privKey.SealTo(pubKey, b), nil return privKey.Seal(b), nil
} }
func unmarshal(msg []byte, v interface{}, publicKey key.MachinePublic, privateKey key.MachinePrivate) error { func unmarshal(msg []byte, v interface{}, privateKey key.ServerPrivate) error {
decrypted, ok := privateKey.OpenFrom(publicKey, msg) decrypted, ok := privateKey.Open(msg)
if !ok { if !ok {
return fmt.Errorf("unable to decrypt payload") return fmt.Errorf("unable to decrypt payload")
} }
+3 -22
View File
@@ -3,19 +3,14 @@ package ionscale
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/token" "github.com/jsiebens/ionscale/internal/token"
"github.com/jsiebens/ionscale/internal/util"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"tailscale.com/types/key"
) )
func HasCredentials(systemAdminKey string) bool {
return systemAdminKey != ""
}
func LoadClientAuth(systemAdminKey string) (ClientAuth, error) { func LoadClientAuth(systemAdminKey string) (ClientAuth, error) {
if systemAdminKey != "" { if systemAdminKey != "" {
k, err := util.ParseMachinePrivateKey(systemAdminKey) k, err := key.ParsePrivateKey(systemAdminKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid system admin key") return nil, fmt.Errorf("invalid system admin key")
} }
@@ -41,7 +36,7 @@ func (m *anonymous) RequireTransportSecurity() bool {
} }
type systemAdminTokenAuth struct { type systemAdminTokenAuth struct {
key key.MachinePrivate key key.ServerPrivate
} }
func (m *systemAdminTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { func (m *systemAdminTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
@@ -57,17 +52,3 @@ func (m *systemAdminTokenAuth) GetRequestMetadata(ctx context.Context, uri ...st
func (m *systemAdminTokenAuth) RequireTransportSecurity() bool { func (m *systemAdminTokenAuth) RequireTransportSecurity() bool {
return false return false
} }
type tokenAuth struct {
token string
}
func (m *tokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"authorization": "Bearer " + m.token,
}, nil
}
func (m *tokenAuth) RequireTransportSecurity() bool {
return false
}