Files
graph/x/acl_enc_keys.go
zeekay f5aaeb712e Hanzo Graph — the engine, on zapdb, ZAP-native
Derived from Dgraph (github.com/dgraph-io/dgraph) at commit 64804bd2, Apache-2.0.
See NOTICE.

WHAT THIS IS. The graph engine as a Go library: posting lists, the DQL parser,
the type and tokenizer system, the index and schema layers, and the wire types.
39,170 lines of the upstream's 166,337.

THE STORE is github.com/luxfi/zapdb, a Badger fork that kept the package name,
the subpackages (options, pb, y) and the API — Open, OpenManaged, DB, Options —
so the 74 import sites are aliased rather than rewritten.

THE CODEC is ZAP. zapdb serializes with ZAP, not protobuf: its pb package builds
the ZAP types by default and the protobuf ones only under `-tags grpc`, a path
that does not compile (table/builder.go calls pb.Marshal, which that tag
removes). One call site in posting needed converting; every other type here is
Hanzo's own protobuf and is untouched.

NO TRANSPORT. An embedded engine dials nothing, so nothing here imports
google.golang.org/grpc. Removed: 20 transport functions in x, the generated
service stubs for Raft, Zero and Worker (1,657 lines), and the JWT helper —
identity belongs to IAM. The core called none of them.

NOT KEPT: worker, conn, raftwal, the cluster commands, the GraphQL layer and the
test harnesses. `query`, the DQL execution engine, went with them because it
depends on worker; parsing DQL is available here, executing it is not.

The history of the upstream project lives at github.com/dgraph-io/dgraph. This
repository begins at the derivation because three quarters of that history
describes code this fork does not carry.
2026-08-20 21:26:27 -07:00

184 lines
5.4 KiB
Go

/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
package x
import (
"crypto"
"crypto/ed25519"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/dgraph-io/ristretto/v2/z"
)
// Keys holds the configuration for ACL and encryption.
type Keys struct {
AclJwtAlg jwt.SigningMethod
AclSecretKey interface{}
AclPublicKey interface{}
AclSecretKeyBytes Sensitive // we need this to compute hash for auth
AclAccessTtl time.Duration
AclRefreshTtl time.Duration
EncKey Sensitive
}
// GetEncAclKeys returns the ACL and encryption keys as configured by the user
// through the --acl, --encryption, and --vault flags.
func GetEncAclKeys(config *viper.Viper) (*Keys, error) {
aclSuperFlag := z.NewSuperFlag(config.GetString("acl")).MergeAndCheckDefault(AclDefaults)
encSuperFlag := z.NewSuperFlag(config.GetString("encryption")).MergeAndCheckDefault(EncDefaults)
// Get SecretKey and EncKey from vault / acl / encryption SuperFlags
aclKey, encKey := vaultGetKeys(config)
encKeyFile := encSuperFlag.GetPath(flagEncKeyFile)
if encKeyFile != "" {
if encKey != nil {
return nil, fmt.Errorf("flags: Encryption key set in both vault and encryption flags")
}
var err error
if encKey, err = os.ReadFile(encKeyFile); err != nil {
return nil, fmt.Errorf("error reading encryption key from file: %s: %s", encKeyFile, err)
}
}
if l := len(encKey); encKey != nil && l != 16 && l != 32 && l != 64 {
return nil, fmt.Errorf("encryption key must have length of 16, 32, or 64 bytes, got %d bytes instead", l)
}
aclSecretFile := aclSuperFlag.GetPath(flagAclKeyFile)
if aclSecretFile != "" {
if aclKey != nil {
return nil, fmt.Errorf("flags: ACL secret key set in both vault and acl flags")
}
var err error
if aclKey, err = os.ReadFile(aclSecretFile); err != nil {
return nil, fmt.Errorf("error reading ACL secret key from file: %s: %s", aclSecretFile, err)
}
}
keys := &Keys{
AclSecretKeyBytes: aclKey,
AclAccessTtl: aclSuperFlag.GetDuration(flagAclAccessTtl),
AclRefreshTtl: aclSuperFlag.GetDuration(flagAclRefreshTtl),
EncKey: encKey,
}
if aclKey != nil {
algStr := aclSuperFlag.GetString(flagAclJwtAlg)
aclAlg := jwt.GetSigningMethod(algStr)
if aclAlg == nil {
return nil, fmt.Errorf("Unsupported JWT signing algorithm for ACL: %v", algStr)
}
if err := checkAclKeyLength(aclAlg, aclKey); err != nil {
return nil, err
}
privKey, pubKey, err := parseJWTKey(aclAlg, aclKey)
if err != nil {
return nil, err
}
keys.AclJwtAlg = aclAlg
keys.AclSecretKey = privKey
keys.AclPublicKey = pubKey
}
return keys, nil
}
func parseJWTKey(alg jwt.SigningMethod, key Sensitive) (interface{}, interface{}, error) {
switch {
case strings.HasPrefix(alg.Alg(), "HS"):
return key, key, nil
case strings.HasPrefix(alg.Alg(), "ES"):
pk, err := jwt.ParseECPrivateKeyFromPEM(key)
if err != nil {
return nil, nil, errors.Wrapf(err, "error parsing ACL key as ECDSA private key")
}
return pk, &pk.PublicKey, nil
case strings.HasPrefix(alg.Alg(), "RS") || strings.HasPrefix(alg.Alg(), "PS"):
pk, err := jwt.ParseRSAPrivateKeyFromPEM(key)
if err != nil {
return nil, nil, errors.Wrapf(err, "error parsing ACL key as RSA private key")
}
return pk, &pk.PublicKey, nil
case alg.Alg() == "EdDSA":
pk, err := jwt.ParseEdPrivateKeyFromPEM(key)
if err != nil {
return nil, nil, errors.Wrapf(err, "error parsing ACL key as EdDSA private key")
}
return pk.(crypto.Signer), pk.(ed25519.PrivateKey).Public(), nil
default:
return nil, nil, errors.Errorf("unsupported signing algorithm: %v", alg.Alg())
}
}
func checkAclKeyLength(alg jwt.SigningMethod, key Sensitive) error {
if !strings.HasPrefix(alg.Alg(), "HS") {
return nil
}
sl, err := strconv.Atoi(strings.TrimPrefix(alg.Alg(), "HS"))
if err != nil {
return errors.Wrapf(err, "error finding sha length for algo %v", alg.Alg())
}
// SHA length has to be smaller or equal to the key length
if sl > len(key)*8 {
return errors.Errorf("ACL key length [%v <= %v] bits for JWT algorithm [%v]", len(key)*8, sl, alg.Alg())
}
return nil
}
func RegisterAclAndEncFlags(flag *pflag.FlagSet) {
registerAclFlag(flag)
registerEncFlag(flag)
registerVaultFlag(flag, true, true)
}
func RegisterEncFlag(flag *pflag.FlagSet) {
registerEncFlag(flag)
registerVaultFlag(flag, false, true)
}
func registerAclFlag(flag *pflag.FlagSet) {
helpText := z.NewSuperFlagHelp(AclDefaults).
Head("ACL options").
Flag("jwt-alg", "JWT signing algorithm, default HS256").
Flag("secret-file",
"The file that stores secret key or private key, which is used to sign the ACL JWT.").
Flag("access-ttl",
"The TTL for the access JWT.").
Flag("refresh-ttl",
"The TTL for the refresh JWT.").
String()
flag.String(flagAcl, AclDefaults, helpText)
}
func registerEncFlag(flag *pflag.FlagSet) {
helpText := z.NewSuperFlagHelp(EncDefaults).
Head("Encryption At Rest options").
Flag("key-file", "The file that stores the symmetric key of length 16, 24, or 32 bytes."+
"The key size determines the chosen AES cipher (AES-128, AES-192, and AES-256 respectively).").
String()
flag.String(flagEnc, EncDefaults, helpText)
}
func BuildEncFlag(filename string) string {
return fmt.Sprintf("key-file=%s;", filename)
}