Skip to content

Commit fee40cb

Browse files
authored
Merge pull request #523 from NERSC/callout-update
update callout to avoid use of APP.nk and CALLOUT.nk
2 parents adabfc9 + 25edac2 commit fee40cb

File tree

6 files changed

+71
-55
lines changed

6 files changed

+71
-55
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
CALLOUT_ACCOUNT_NKEY_FILE=/nats-conf/CALLOUT.nk
1+
CALLOUT_ACCOUNT_PUBLIC_KEY_FILE=/nats-conf/CALLOUT.pub
22
CALLOUT_ACCOUNT_SIGNING_KEY_FILE=/nats-conf/CALLOUT_sk.nk
33
CALLOUT_ACCOUNT_XKEY_FILE=/nats-conf/CALLOUT_xkey.nk
44
CALLOUT_USER_CREDS_FILE=/nats-conf/callout.creds
5-
APP_ACCOUNT_NKEY_FILE=/nats-conf/APP.nk
5+
APP_ACCOUNT_PUBLIC_KEY_FILE=/nats-conf/APP.pub
66
APP_ACCOUNT_SIGNING_KEY_FILE=/nats-conf/APP_sk.nk
77
SERVER_URL=nats://nats1:4222
88
TOKEN_EXPIRATION_TIME_S=600
99
JWT_SECRET_KEYS=changethis
10-
JWT_ALGORITHM=HS256
10+
JWT_ALGORITHM=HS256

backend/callout/service/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ module distiller-callout
33
go 1.24.0
44

55
require (
6-
github.com/aricart/callout.go v0.2.0
76
github.com/go-playground/validator/v10 v10.25.0
87
github.com/golang-jwt/jwt/v5 v5.2.2
98
github.com/joho/godotenv v1.5.1
109
github.com/nats-io/jwt/v2 v2.7.3
1110
github.com/nats-io/nats-server/v2 v2.11.1
12-
github.com/nats-io/nats.go v1.39.1
11+
github.com/nats-io/nats.go v1.40.1
1312
github.com/nats-io/nkeys v0.4.10
13+
github.com/synadia-io/callout.go v0.2.1
1414
)
1515

1616
require (

backend/callout/service/go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
22
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
33
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0=
44
github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E=
5-
github.com/aricart/callout.go v0.2.0 h1:PdC+1DU/FxzB0jzGjrVsppkDo85qpLNzZeAZz9DrKAo=
6-
github.com/aricart/callout.go v0.2.0/go.mod h1:MOOfW150p/M1eELg1Gs5IvTBjS3jNhbrlJrMstaEDPE=
75
github.com/aricart/nst.go v0.1.0 h1:GqLjCGFd02hJCdL96rVwtkRTXAajokV5sgikB5BQ7NQ=
86
github.com/aricart/nst.go v0.1.0/go.mod h1:N0yWlAR0nNa+Bkl2onPbOi9+LqXmcwg2WBZKHKanbyk=
97
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -36,8 +34,8 @@ github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE=
3634
github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4=
3735
github.com/nats-io/nats-server/v2 v2.11.1 h1:LwdauqMqMNhTxTN3+WFTX6wGDOKntHljgZ+7gL5HCnk=
3836
github.com/nats-io/nats-server/v2 v2.11.1/go.mod h1:leXySghbdtXSUmWem8K9McnJ6xbJOb0t9+NQ5HTRZjI=
39-
github.com/nats-io/nats.go v1.39.1 h1:oTkfKBmz7W047vRxV762M67ZdXeOtUgvbBaNoQ+3PPk=
40-
github.com/nats-io/nats.go v1.39.1/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM=
37+
github.com/nats-io/nats.go v1.40.1 h1:MLjDkdsbGUeCMKFyCFoLnNn/HDTqcgVa3EQm+pMNDPk=
38+
github.com/nats-io/nats.go v1.40.1/go.mod h1:wV73x0FSI/orHPSYoyMeJB+KajMDoWyXmFaRrrYaaTo=
4139
github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc=
4240
github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U=
4341
github.com/nats-io/nsc/v2 v2.10.3-0.20250110165315-eeda721ecff6 h1:V1uh9L3rGIUeYoMd0/EZY2Tvy5+1CkgGpDRx8t3+PNY=
@@ -48,6 +46,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
4846
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4947
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
5048
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
49+
github.com/synadia-io/callout.go v0.2.1 h1:apV+t85RvP3c6rUZwCD1txeoGJvYmhndvpOc5sCmqvQ=
50+
github.com/synadia-io/callout.go v0.2.1/go.mod h1:JklDEQ+1QXw05NEokqE/OM1LTv4yBqoL33ZDBDEntgg=
5151
github.com/synadia-io/jwt-auth-builder.go v0.0.4 h1:cfTMDAa9iylnD/O6kXqE8Mk51F36kyuQ6BhRrT1svfI=
5252
github.com/synadia-io/jwt-auth-builder.go v0.0.4/go.mod h1:8WYR7+nLQcDMBpocuPgdFJ5/2UOr+HPll3qv+KNdGvs=
5353
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=

backend/callout/service/main.go

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,23 @@ import (
1313
"syscall"
1414
"time"
1515

16-
"github.com/aricart/callout.go"
1716
"github.com/go-playground/validator/v10"
1817
"github.com/golang-jwt/jwt/v5"
1918
"github.com/joho/godotenv"
2019
natsjwt "github.com/nats-io/jwt/v2"
2120
nslogger "github.com/nats-io/nats-server/v2/logger"
2221
"github.com/nats-io/nats.go"
2322
"github.com/nats-io/nkeys"
23+
"github.com/synadia-io/callout.go"
2424
)
2525

2626
type Config struct {
2727
VERIFY_URL string `validate:"omitempty,url"`
28-
CALLOUT_ACCOUNT_NKEY_FILE string `validate:"required,filepath"`
28+
CALLOUT_ACCOUNT_PUBLIC_KEY_FILE string `validate:"required,filepath"`
2929
CALLOUT_ACCOUNT_SIGNING_KEY_FILE string `validate:"required,filepath"`
3030
CALLOUT_ACCOUNT_XKEY_FILE string `validate:"required,filepath"`
3131
CALLOUT_USER_CREDS_FILE string `validate:"required,filepath"`
32-
APP_ACCOUNT_NKEY_FILE string `validate:"required,filepath"`
32+
APP_ACCOUNT_PUBLIC_KEY_FILE string `validate:"required,filepath"`
3333
APP_ACCOUNT_SIGNING_KEY_FILE string `validate:"required,filepath"`
3434
SERVER_URL string `validate:"required,url"`
3535
TOKEN_EXPIRATION_TIME_S int `validate:"required"`
@@ -48,11 +48,11 @@ func main() {
4848

4949
cfg := Config{
5050
VERIFY_URL: os.Getenv("VERIFY_URL"),
51-
CALLOUT_ACCOUNT_NKEY_FILE: os.Getenv("CALLOUT_ACCOUNT_NKEY_FILE"),
51+
CALLOUT_ACCOUNT_PUBLIC_KEY_FILE: os.Getenv("CALLOUT_ACCOUNT_PUBLIC_KEY_FILE"),
5252
CALLOUT_ACCOUNT_SIGNING_KEY_FILE: os.Getenv("CALLOUT_ACCOUNT_SIGNING_KEY_FILE"),
5353
CALLOUT_ACCOUNT_XKEY_FILE: os.Getenv("CALLOUT_ACCOUNT_XKEY_FILE"),
5454
CALLOUT_USER_CREDS_FILE: os.Getenv("CALLOUT_USER_CREDS_FILE"),
55-
APP_ACCOUNT_NKEY_FILE: os.Getenv("APP_ACCOUNT_NKEY_FILE"),
55+
APP_ACCOUNT_PUBLIC_KEY_FILE: os.Getenv("APP_ACCOUNT_PUBLIC_KEY_FILE"),
5656
APP_ACCOUNT_SIGNING_KEY_FILE: os.Getenv("APP_ACCOUNT_SIGNING_KEY_FILE"),
5757
SERVER_URL: os.Getenv("SERVER_URL"),
5858
TOKEN_EXPIRATION_TIME_S: func() int {
@@ -75,42 +75,38 @@ func main() {
7575
logger.Noticef("config validated")
7676

7777
// Keys description:
78-
// calloutAccountKP: signs callout responses.
79-
// should be **account key** of the callout account
78+
// calloutAccountPublicKey: issuer for callout responses.
79+
// should be **public key** of the callout account
80+
// calloutAccountSigningKeyKP: signs callout responses.
81+
// should be **signing key** of the callout account
8082
// calloutEncryptionKP: encrypts callout responses.
8183
// should be **encryption key** of the callout account
82-
// appAccountKP: key pair for assigning account to the user
83-
// should be **account key** of the app account
84+
// appAccountPublicKey: account assignment for the user
85+
// should be **public key** of the app account
8486
// appAccountSigningKeyKP: key pair for signing the user
8587
// should be **signing key** of the app account
86-
calloutAccountKP, err := loadAndParseKeys(cfg.CALLOUT_ACCOUNT_NKEY_FILE)
88+
calloutAccountSigningKeyKP, err := loadAndParseKeys(cfg.CALLOUT_ACCOUNT_SIGNING_KEY_FILE)
8789
if err != nil {
88-
panic(fmt.Errorf("error creating issuer key pair: %v", err))
90+
panic(fmt.Errorf("error creating signing key pair: %v", err))
8991
}
90-
91-
// TODO: determine how avoid using calloutAccountKP
92-
// calloutAccountSigningKeyKP, err := loadAndParseKeys(cfg.CALLOUT_ACCOUNT_SIGNING_KEY_FILE)
93-
// if err != nil {
94-
// panic(fmt.Errorf("error creating signing key pair: %v", err))
95-
// }
9692
calloutEncryptionKP, err := loadAndParseKeys(cfg.CALLOUT_ACCOUNT_XKEY_FILE)
9793
if err != nil {
9894
panic(fmt.Errorf("error creating encryption key pair: %v", err))
9995
}
100-
appAccountKP, err := loadAndParseKeys(cfg.APP_ACCOUNT_NKEY_FILE)
101-
if err != nil {
102-
panic(fmt.Errorf("error creating account key pair: %v", err))
103-
}
10496
appAccountSigningKeyKP, err := loadAndParseKeys(cfg.APP_ACCOUNT_SIGNING_KEY_FILE)
10597
if err != nil {
10698
panic(fmt.Errorf("error creating account signing key pair: %v", err))
10799
}
108100

109101
logger.Noticef("keys loaded")
110102

111-
appAccountPublicKey, err := appAccountKP.PublicKey()
103+
calloutAccountPublicKey, err := loadPublicAccountKey(cfg.CALLOUT_ACCOUNT_PUBLIC_KEY_FILE)
104+
if err != nil {
105+
panic(fmt.Errorf("error loading callout account public key: %v", err))
106+
}
107+
appAccountPublicKey, err := loadPublicAccountKey(cfg.APP_ACCOUNT_PUBLIC_KEY_FILE)
112108
if err != nil {
113-
panic(fmt.Errorf("error getting app account public key: %v", err))
109+
panic(fmt.Errorf("error loading app account public key: %v", err))
114110
}
115111

116112
// Function that creates the users
@@ -183,7 +179,9 @@ func main() {
183179
_, err = callout.NewAuthorizationService(nc,
184180
callout.Authorizer(authorizer),
185181
// Sign the response with the callout account's signing key
186-
callout.ResponseSignerKey(calloutAccountKP),
182+
callout.ResponseSignerKey(calloutAccountSigningKeyKP),
183+
// Let NATS verify the signing key against the callout account
184+
callout.ResponseSignerIssuer(calloutAccountPublicKey),
187185
// Encrypt the response with the callout account's encryption key
188186
callout.EncryptionKey(calloutEncryptionKP))
189187

@@ -198,23 +196,23 @@ func main() {
198196
}
199197

200198
func VerifyTokenLocally(token string, secretKeys []string, algorithm string) (*jwt.Token, error) {
201-
var lastErr error
202-
203-
for _, secretKey := range secretKeys {
204-
parsedToken, err := jwt.Parse(token, func(pToken *jwt.Token) (interface{}, error) {
205-
if pToken.Method.Alg() != algorithm {
206-
return nil, fmt.Errorf("unexpected signing method: %v", pToken.Header["alg"])
207-
}
208-
return []byte(secretKey), nil
209-
})
210-
211-
if err == nil && parsedToken.Valid {
212-
return parsedToken, nil
213-
}
214-
lastErr = err
215-
}
216-
217-
return nil, fmt.Errorf("failed to verify token with any key: %v", lastErr)
199+
var lastErr error
200+
201+
for _, secretKey := range secretKeys {
202+
parsedToken, err := jwt.Parse(token, func(pToken *jwt.Token) (interface{}, error) {
203+
if pToken.Method.Alg() != algorithm {
204+
return nil, fmt.Errorf("unexpected signing method: %v", pToken.Header["alg"])
205+
}
206+
return []byte(secretKey), nil
207+
})
208+
209+
if err == nil && parsedToken.Valid {
210+
return parsedToken, nil
211+
}
212+
lastErr = err
213+
}
214+
215+
return nil, fmt.Errorf("failed to verify token with any key: %v", lastErr)
218216
}
219217

220218
func VerifyToken(token string, verificationUrl string) error {
@@ -239,6 +237,24 @@ func VerifyToken(token string, verificationUrl string) error {
239237
return nil
240238
}
241239

240+
func loadPublicAccountKey(fp string) (string, error) {
241+
if fp == "" {
242+
return "", errors.New("public key file required")
243+
}
244+
data, err := os.ReadFile(fp)
245+
if err != nil {
246+
return "", fmt.Errorf("error reading public key file: %w", err)
247+
}
248+
key := strings.TrimSpace(string(data))
249+
if key == "" {
250+
return "", errors.New("public key file is empty")
251+
}
252+
if !nkeys.IsValidPublicAccountKey(key) {
253+
return "", errors.New("public key must be an account public key")
254+
}
255+
return key, nil
256+
}
257+
242258
func loadAndParseKeys(fp string) (nkeys.KeyPair, error) {
243259
if fp == "" {
244260
return nil, errors.New("key file required")

conf/nats-conf/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ sequenceDiagram
4545
The script generates the following artifacts to `./out_jwt`:
4646

4747
- `auth.conf`: configuration generated after all accounts are added, imported in nats cluster configuration files
48-
- `APP.nk`: ___APP___ `account` private NKEY. Used to extract public NKEY and assign it to users (IssuerAccount).
48+
- `APP.pub`: ___APP___ `account` public NKEY. Used to assign users to the APP account (IssuerAccount).
4949
- `APP_sk.nk`: ___APP___ `account` signing private NKEY. Used to sign users in auth callout.
50-
- `CALLOUT.nk`: ___CALLOUT___ `account` private NKEY. Used to sign auth callout responses.
51-
- `CALLOUT_sk.nk`: ___CALLOUT___ `account` signing private NKEY. Currently doesn't do anything
50+
- `CALLOUT.pub`: ___CALLOUT___ `account` public NKEY. Used as the auth callout response issuer.
51+
- `CALLOUT_sk.nk`: ___CALLOUT___ `account` signing private NKEY. Used to sign auth callout responses.
5252
- `CALLOUT_xkey.nk`: ___CALLOUT___ `account` encryption NKEY
5353
- `backend.creds`: backend `user` credentials used by backend services (FastAPI, orchestrator, agents)
5454
- `callout.creds`: callout `user` credentials used by callout service

conf/nats-conf/generate-auth-jwt.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,9 @@ cp "$NSC_WORK_DIR"/*.creds "$CP_DIR"/
105105
cp "$NSC_WORK_DIR/$AUTH_CONF_FILENAME" "$CP_DIR/$AUTH_CONF_FILENAME"
106106

107107
NSC_KEYS_BASE="$XDG_DATA_HOME/nats/nsc/keys/keys"
108-
cp "$NSC_KEYS_BASE/A/${APP_ACCOUNT:1:2}/${APP_ACCOUNT}.nk" "$CP_DIR/APP.nk"
108+
printf "%s\n" "$APP_ACCOUNT" > "$CP_DIR/APP.pub"
109109
cp "$NSC_KEYS_BASE/A/${APP_ACCOUNT_SK:1:2}/${APP_ACCOUNT_SK}.nk" "$CP_DIR/APP_sk.nk"
110-
cp "$NSC_KEYS_BASE/A/${CALLOUT_ACCOUNT:1:2}/${CALLOUT_ACCOUNT}.nk" "$CP_DIR/CALLOUT.nk"
110+
printf "%s\n" "$CALLOUT_ACCOUNT" > "$CP_DIR/CALLOUT.pub"
111111
cp "$NSC_KEYS_BASE/A/${CALLOUT_ACCOUNT_SK:1:2}/${CALLOUT_ACCOUNT_SK}.nk" "$CP_DIR/CALLOUT_sk.nk"
112112
cp "$NSC_KEYS_BASE/X/${CALLOUT_ACCOUNT_XKEY:1:2}/${CALLOUT_ACCOUNT_XKEY}.nk" "$CP_DIR/CALLOUT_xkey.nk"
113113

0 commit comments

Comments
 (0)