mirror of
https://github.com/jsiebens/ionscale.git
synced 2026-03-31 15:07:49 +01:00
chore: introduce server key
This commit is contained in:
+23
-24
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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
@@ -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,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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user