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 (
"fmt"
"github.com/caddyserver/certmagic"
"github.com/jsiebens/ionscale/internal/util"
"github.com/jsiebens/ionscale/internal/key"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3"
"io/ioutil"
"strings"
"tailscale.com/types/key"
)
func LoadConfig(path string) (*Config, error) {
@@ -83,43 +82,43 @@ func defaultConfig() *Config {
}
type ServerKeys struct {
SystemAdminKey key.MachinePrivate
SystemAdminKey key.ServerPrivate
}
type Config struct {
HttpListenAddr string `yaml:"http_listen_addr"`
HttpsListenAddr string `yaml:"https_listen_addr"`
MetricsListenAddr string `yaml:"metrics_listen_addr"`
ServerUrl string `yaml:"server_url"`
Tls Tls `yaml:"tls"`
Logging Logging `yaml:"logging"`
Keys Keys `yaml:"keys"`
Database Database `yaml:"database"`
HttpListenAddr string `yaml:"http_listen_addr,omitempty"`
HttpsListenAddr string `yaml:"https_listen_addr,omitempty"`
MetricsListenAddr string `yaml:"metrics_listen_addr,omitempty"`
ServerUrl string `yaml:"server_url,omitempty"`
Tls Tls `yaml:"tls,omitempty"`
Logging Logging `yaml:"logging,omitempty"`
Keys Keys `yaml:"keys,omitempty"`
Database Database `yaml:"database,omitempty"`
}
type Tls struct {
Disable bool `yaml:"disable"`
CertFile string `yaml:"cert_file"`
KeyFile string `yaml:"key_file"`
CertMagicDomain string `yaml:"cert_magic_domain"`
CertMagicEmail string `yaml:"cert_magic_email"`
CertMagicCA string `yaml:"cert_magic_ca"`
CertMagicStoragePath string `yaml:"cert_magic_storage_path"`
CertFile string `yaml:"cert_file,omitempty"`
KeyFile string `yaml:"key_file,omitempty"`
CertMagicDomain string `yaml:"cert_magic_domain,omitempty"`
CertMagicEmail string `yaml:"cert_magic_email,omitempty"`
CertMagicCA string `yaml:"cert_magic_ca,omitempty"`
CertMagicStoragePath string `yaml:"cert_magic_storage_path,omitempty"`
}
type Logging struct {
Level string `yaml:"level"`
Format string `yaml:"format"`
File string `yaml:"file"`
Level string `yaml:"level,omitempty"`
Format string `yaml:"format,omitempty"`
File string `yaml:"file,omitempty"`
}
type Database struct {
Url string `yaml:"url"`
Url string `yaml:"url,omitempty"`
}
type Keys struct {
SystemAdminKey string `yaml:"system_admin_key"`
EncryptionKey string `yaml:"encryption_key"`
SystemAdminKey string `yaml:"system_admin_key,omitempty"`
EncryptionKey string `yaml:"encryption_key,omitempty"`
}
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) {
systemAdminKey, err := util.ParseMachinePrivateKey(c.Keys.SystemAdminKey)
systemAdminKey, err := key.ParsePrivateKey(c.Keys.SystemAdminKey)
if err != nil {
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"
"net/http"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
tskey "tailscale.com/types/key"
"time"
"github.com/jsiebens/ionscale/internal/config"
@@ -106,8 +106,8 @@ func initializeControlKeys(repository domain.Repository) error {
}
keys = &domain.ControlKeys{
ControlKey: key.NewMachine(),
LegacyControlKey: key.NewMachine(),
ControlKey: tskey.NewMachine(),
LegacyControlKey: tskey.NewMachine(),
}
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-prometheus"
"github.com/hashicorp/go-hclog"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/service"
"google.golang.org/grpc"
"tailscale.com/types/key"
)
func init() {
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(
middleware.WithUnaryServerChain(
logging.UnaryServerInterceptor(
+3 -3
View File
@@ -4,6 +4,7 @@ import (
"context"
"github.com/jsiebens/ionscale/internal/broker"
"github.com/jsiebens/ionscale/internal/domain"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/token"
"github.com/jsiebens/ionscale/internal/version"
"github.com/jsiebens/ionscale/pkg/gen/api"
@@ -12,7 +13,6 @@ import (
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"strings"
"tailscale.com/types/key"
)
var (
@@ -44,7 +44,7 @@ func (s *Service) GetVersion(ctx context.Context, req *api.GetVersionRequest) (*
}, 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) {
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 {
return false
}
+11 -11
View File
@@ -5,10 +5,10 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/util"
"github.com/mr-tron/base58"
"strings"
"tailscale.com/types/key"
"time"
)
@@ -29,7 +29,7 @@ func IsSystemAdminToken(token string) bool {
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)
if versionedToken == "" {
return nil, errors.New("empty token")
@@ -50,7 +50,7 @@ func ParseSystemAdminToken(privKey key.MachinePrivate, versionedToken string) (*
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)
}
@@ -77,7 +77,7 @@ func ParseSystemAdminToken(privKey key.MachinePrivate, versionedToken string) (*
return info, nil
}
func GenerateSystemAdminToken(privKey key.MachinePrivate) (string, error) {
func GenerateSystemAdminToken(privKey key.ServerPrivate) (string, error) {
b, err := util.RandomBytes(nonceLength)
if err != nil {
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(),
}
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) {
blobInfo, err := marshal(v, pubKey, privKey)
func formatToken(privKey key.ServerPrivate, prefix string, v interface{}) (string, error) {
blobInfo, err := marshal(v, privKey)
if err != nil {
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
}
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)
if err != nil {
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 {
decrypted, ok := privateKey.OpenFrom(publicKey, msg)
func unmarshal(msg []byte, v interface{}, privateKey key.ServerPrivate) error {
decrypted, ok := privateKey.Open(msg)
if !ok {
return fmt.Errorf("unable to decrypt payload")
}
+3 -22
View File
@@ -3,19 +3,14 @@ package ionscale
import (
"context"
"fmt"
"github.com/jsiebens/ionscale/internal/key"
"github.com/jsiebens/ionscale/internal/token"
"github.com/jsiebens/ionscale/internal/util"
"google.golang.org/grpc/credentials"
"tailscale.com/types/key"
)
func HasCredentials(systemAdminKey string) bool {
return systemAdminKey != ""
}
func LoadClientAuth(systemAdminKey string) (ClientAuth, error) {
if systemAdminKey != "" {
k, err := util.ParseMachinePrivateKey(systemAdminKey)
k, err := key.ParsePrivateKey(systemAdminKey)
if err != nil {
return nil, fmt.Errorf("invalid system admin key")
}
@@ -41,7 +36,7 @@ func (m *anonymous) RequireTransportSecurity() bool {
}
type systemAdminTokenAuth struct {
key key.MachinePrivate
key key.ServerPrivate
}
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 {
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
}