mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
multi: preserve macaroon recipe order in SQL
Store explicit positions for session macaroon caveats and permissions in the SQL schema and read them back in position order. Also remove the migration-time sorting workaround in session comparison, so migration validation now checks the actual persisted order instead of masking it. This is needed because session caveat order is not just presentation data. LiT adds caveats to the baked macaroon in slice order, and the macaroon library updates the signature hash chain for each added caveat. Reordering caveats can therefore change the resulting macaroon bytes and signature. The previous schema split caveats and permissions into child tables without any position column, and the SQL reads had no ORDER BY. The KV store preserves slice order, but SQL had no explicit way to reproduce that order after migration or on later reads. The migration code’s old sorting step was only making validation deterministic; it did not preserve the original recipe order. Permissions are canonicalized by lnd when baking, so their order is less semantically important for the final macaroon. They still get positions here so the stored recipe remains faithful to the original session data and both child tables behave consistently. Why it was needed: - caveats needed explicit order preservation because they are appended and signed in order. - The old SQL schema did not store order, and the read queries did not request one. - Adding position makes the SQL representation faithful to the KV/TLV recipe instead of relying on incidental row order. - Adding it to permissions too keeps the stored recipe lossless and consistent, even though lnd. canonicalizes permissions before baking. NOTE: This commit explicitly edits the previous migration instead of adding a new one. This is ok as SQL dbs are not yet supported in production, so there are no live deployments to worry about.
This commit is contained in:
parent
e955dc825b
commit
b61f62d7d9
8 changed files with 76 additions and 85 deletions
|
|
@ -84,9 +84,13 @@ CREATE TABLE IF NOT EXISTS session_macaroon_permissions (
|
|||
entity TEXT NOT NULL,
|
||||
|
||||
-- The action that this permission is for.
|
||||
action TEXT NOT NULL
|
||||
action TEXT NOT NULL,
|
||||
|
||||
-- The original position of the permission in the session recipe.
|
||||
position INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS sessions_mac_perms_idx ON session_macaroon_permissions(session_id);
|
||||
CREATE INDEX IF NOT EXISTS sessions_mac_perms_idx
|
||||
ON session_macaroon_permissions(session_id, position);
|
||||
|
||||
-- The session_macaroon_caveats table contains the macaroon caveats that are
|
||||
-- associated with a session.
|
||||
|
|
@ -105,10 +109,14 @@ CREATE TABLE IF NOT EXISTS session_macaroon_caveats (
|
|||
verification_id BLOB,
|
||||
|
||||
-- The location hint for third party caveats.
|
||||
location TEXT
|
||||
location TEXT,
|
||||
|
||||
-- The original position of the caveat in the session recipe.
|
||||
position INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS sessions_mac_caveats_idx ON session_macaroon_caveats(session_id);
|
||||
CREATE INDEX IF NOT EXISTS sessions_mac_caveats_idx
|
||||
ON session_macaroon_caveats(session_id, position);
|
||||
|
||||
-- The session_feature_configs table contains the feature configs that are
|
||||
-- associated with a session.
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ type SessionMacaroonCaveat struct {
|
|||
CaveatID []byte
|
||||
VerificationID []byte
|
||||
Location sql.NullString
|
||||
Position int32
|
||||
}
|
||||
|
||||
type SessionMacaroonPermission struct {
|
||||
|
|
@ -120,6 +121,7 @@ type SessionMacaroonPermission struct {
|
|||
SessionID int64
|
||||
Entity string
|
||||
Action string
|
||||
Position int32
|
||||
}
|
||||
|
||||
type SessionPrivacyFlag struct {
|
||||
|
|
|
|||
|
|
@ -81,25 +81,27 @@ WHERE id = $2;
|
|||
|
||||
-- name: InsertSessionMacaroonPermission :exec
|
||||
INSERT INTO session_macaroon_permissions (
|
||||
session_id, entity, action
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
);
|
||||
|
||||
-- name: GetSessionMacaroonPermissions :many
|
||||
SELECT * FROM session_macaroon_permissions
|
||||
WHERE session_id = $1;
|
||||
|
||||
-- name: InsertSessionMacaroonCaveat :exec
|
||||
INSERT INTO session_macaroon_caveats (
|
||||
session_id, caveat_id, verification_id, location
|
||||
session_id, entity, action, position
|
||||
) VALUES (
|
||||
$1, $2, $3, $4
|
||||
);
|
||||
|
||||
-- name: GetSessionMacaroonPermissions :many
|
||||
SELECT * FROM session_macaroon_permissions
|
||||
WHERE session_id = $1
|
||||
ORDER BY position ASC;
|
||||
|
||||
-- name: InsertSessionMacaroonCaveat :exec
|
||||
INSERT INTO session_macaroon_caveats (
|
||||
session_id, caveat_id, verification_id, location, position
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5
|
||||
);
|
||||
|
||||
-- name: GetSessionMacaroonCaveats :many
|
||||
SELECT * FROM session_macaroon_caveats
|
||||
WHERE session_id = $1;
|
||||
WHERE session_id = $1
|
||||
ORDER BY position ASC;
|
||||
|
||||
-- name: InsertSessionFeatureConfig :exec
|
||||
INSERT INTO session_feature_configs (
|
||||
|
|
@ -121,4 +123,4 @@ INSERT INTO session_privacy_flags (
|
|||
|
||||
-- name: GetSessionPrivacyFlags :many
|
||||
SELECT * FROM session_privacy_flags
|
||||
WHERE session_id = $1;
|
||||
WHERE session_id = $1;
|
||||
|
|
|
|||
|
|
@ -205,8 +205,9 @@ func (q *Queries) GetSessionIDByAlias(ctx context.Context, alias []byte) (int64,
|
|||
}
|
||||
|
||||
const getSessionMacaroonCaveats = `-- name: GetSessionMacaroonCaveats :many
|
||||
SELECT id, session_id, caveat_id, verification_id, location FROM session_macaroon_caveats
|
||||
SELECT id, session_id, caveat_id, verification_id, location, position FROM session_macaroon_caveats
|
||||
WHERE session_id = $1
|
||||
ORDER BY position ASC
|
||||
`
|
||||
|
||||
func (q *Queries) GetSessionMacaroonCaveats(ctx context.Context, sessionID int64) ([]SessionMacaroonCaveat, error) {
|
||||
|
|
@ -224,6 +225,7 @@ func (q *Queries) GetSessionMacaroonCaveats(ctx context.Context, sessionID int64
|
|||
&i.CaveatID,
|
||||
&i.VerificationID,
|
||||
&i.Location,
|
||||
&i.Position,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -239,8 +241,9 @@ func (q *Queries) GetSessionMacaroonCaveats(ctx context.Context, sessionID int64
|
|||
}
|
||||
|
||||
const getSessionMacaroonPermissions = `-- name: GetSessionMacaroonPermissions :many
|
||||
SELECT id, session_id, entity, action FROM session_macaroon_permissions
|
||||
SELECT id, session_id, entity, action, position FROM session_macaroon_permissions
|
||||
WHERE session_id = $1
|
||||
ORDER BY position ASC
|
||||
`
|
||||
|
||||
func (q *Queries) GetSessionMacaroonPermissions(ctx context.Context, sessionID int64) ([]SessionMacaroonPermission, error) {
|
||||
|
|
@ -257,6 +260,7 @@ func (q *Queries) GetSessionMacaroonPermissions(ctx context.Context, sessionID i
|
|||
&i.SessionID,
|
||||
&i.Entity,
|
||||
&i.Action,
|
||||
&i.Position,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -422,9 +426,9 @@ func (q *Queries) InsertSessionFeatureConfig(ctx context.Context, arg InsertSess
|
|||
|
||||
const insertSessionMacaroonCaveat = `-- name: InsertSessionMacaroonCaveat :exec
|
||||
INSERT INTO session_macaroon_caveats (
|
||||
session_id, caveat_id, verification_id, location
|
||||
session_id, caveat_id, verification_id, location, position
|
||||
) VALUES (
|
||||
$1, $2, $3, $4
|
||||
$1, $2, $3, $4, $5
|
||||
)
|
||||
`
|
||||
|
||||
|
|
@ -433,6 +437,7 @@ type InsertSessionMacaroonCaveatParams struct {
|
|||
CaveatID []byte
|
||||
VerificationID []byte
|
||||
Location sql.NullString
|
||||
Position int32
|
||||
}
|
||||
|
||||
func (q *Queries) InsertSessionMacaroonCaveat(ctx context.Context, arg InsertSessionMacaroonCaveatParams) error {
|
||||
|
|
@ -441,15 +446,16 @@ func (q *Queries) InsertSessionMacaroonCaveat(ctx context.Context, arg InsertSes
|
|||
arg.CaveatID,
|
||||
arg.VerificationID,
|
||||
arg.Location,
|
||||
arg.Position,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const insertSessionMacaroonPermission = `-- name: InsertSessionMacaroonPermission :exec
|
||||
INSERT INTO session_macaroon_permissions (
|
||||
session_id, entity, action
|
||||
session_id, entity, action, position
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
$1, $2, $3, $4
|
||||
)
|
||||
`
|
||||
|
||||
|
|
@ -457,10 +463,16 @@ type InsertSessionMacaroonPermissionParams struct {
|
|||
SessionID int64
|
||||
Entity string
|
||||
Action string
|
||||
Position int32
|
||||
}
|
||||
|
||||
func (q *Queries) InsertSessionMacaroonPermission(ctx context.Context, arg InsertSessionMacaroonPermissionParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertSessionMacaroonPermission, arg.SessionID, arg.Entity, arg.Action)
|
||||
_, err := q.db.ExecContext(ctx, insertSessionMacaroonPermission,
|
||||
arg.SessionID,
|
||||
arg.Entity,
|
||||
arg.Action,
|
||||
arg.Position,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ type SessionMacaroonCaveat struct {
|
|||
CaveatID []byte
|
||||
VerificationID []byte
|
||||
Location sql.NullString
|
||||
Position int64
|
||||
}
|
||||
|
||||
type SessionMacaroonPermission struct {
|
||||
|
|
@ -116,6 +117,7 @@ type SessionMacaroonPermission struct {
|
|||
SessionID int64
|
||||
Entity string
|
||||
Action string
|
||||
Position int64
|
||||
}
|
||||
|
||||
type SessionPrivacyFlag struct {
|
||||
|
|
|
|||
|
|
@ -200,8 +200,9 @@ func (q *Queries) GetSessionIDByAlias(ctx context.Context, alias []byte) (int64,
|
|||
}
|
||||
|
||||
const getSessionMacaroonCaveats = `-- name: GetSessionMacaroonCaveats :many
|
||||
SELECT id, session_id, caveat_id, verification_id, location FROM session_macaroon_caveats
|
||||
SELECT id, session_id, caveat_id, verification_id, location, position FROM session_macaroon_caveats
|
||||
WHERE session_id = $1
|
||||
ORDER BY position ASC
|
||||
`
|
||||
|
||||
func (q *Queries) GetSessionMacaroonCaveats(ctx context.Context, sessionID int64) ([]SessionMacaroonCaveat, error) {
|
||||
|
|
@ -219,6 +220,7 @@ func (q *Queries) GetSessionMacaroonCaveats(ctx context.Context, sessionID int64
|
|||
&i.CaveatID,
|
||||
&i.VerificationID,
|
||||
&i.Location,
|
||||
&i.Position,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -234,8 +236,9 @@ func (q *Queries) GetSessionMacaroonCaveats(ctx context.Context, sessionID int64
|
|||
}
|
||||
|
||||
const getSessionMacaroonPermissions = `-- name: GetSessionMacaroonPermissions :many
|
||||
SELECT id, session_id, entity, action FROM session_macaroon_permissions
|
||||
SELECT id, session_id, entity, action, position FROM session_macaroon_permissions
|
||||
WHERE session_id = $1
|
||||
ORDER BY position ASC
|
||||
`
|
||||
|
||||
func (q *Queries) GetSessionMacaroonPermissions(ctx context.Context, sessionID int64) ([]SessionMacaroonPermission, error) {
|
||||
|
|
@ -252,6 +255,7 @@ func (q *Queries) GetSessionMacaroonPermissions(ctx context.Context, sessionID i
|
|||
&i.SessionID,
|
||||
&i.Entity,
|
||||
&i.Action,
|
||||
&i.Position,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -417,9 +421,9 @@ func (q *Queries) InsertSessionFeatureConfig(ctx context.Context, arg InsertSess
|
|||
|
||||
const insertSessionMacaroonCaveat = `-- name: InsertSessionMacaroonCaveat :exec
|
||||
INSERT INTO session_macaroon_caveats (
|
||||
session_id, caveat_id, verification_id, location
|
||||
session_id, caveat_id, verification_id, location, position
|
||||
) VALUES (
|
||||
$1, $2, $3, $4
|
||||
$1, $2, $3, $4, $5
|
||||
)
|
||||
`
|
||||
|
||||
|
|
@ -428,6 +432,7 @@ type InsertSessionMacaroonCaveatParams struct {
|
|||
CaveatID []byte
|
||||
VerificationID []byte
|
||||
Location sql.NullString
|
||||
Position int64
|
||||
}
|
||||
|
||||
func (q *Queries) InsertSessionMacaroonCaveat(ctx context.Context, arg InsertSessionMacaroonCaveatParams) error {
|
||||
|
|
@ -436,15 +441,16 @@ func (q *Queries) InsertSessionMacaroonCaveat(ctx context.Context, arg InsertSes
|
|||
arg.CaveatID,
|
||||
arg.VerificationID,
|
||||
arg.Location,
|
||||
arg.Position,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const insertSessionMacaroonPermission = `-- name: InsertSessionMacaroonPermission :exec
|
||||
INSERT INTO session_macaroon_permissions (
|
||||
session_id, entity, action
|
||||
session_id, entity, action, position
|
||||
) VALUES (
|
||||
$1, $2, $3
|
||||
$1, $2, $3, $4
|
||||
)
|
||||
`
|
||||
|
||||
|
|
@ -452,10 +458,13 @@ type InsertSessionMacaroonPermissionParams struct {
|
|||
SessionID int64
|
||||
Entity string
|
||||
Action string
|
||||
Position int64
|
||||
}
|
||||
|
||||
func (q *Queries) InsertSessionMacaroonPermission(ctx context.Context, arg InsertSessionMacaroonPermissionParams) error {
|
||||
_, err := q.db.ExecContext(ctx, insertSessionMacaroonPermission, arg.SessionID, arg.Entity, arg.Action)
|
||||
_, err := q.db.ExecContext(ctx, insertSessionMacaroonPermission,
|
||||
arg.SessionID, arg.Entity, arg.Action, arg.Position,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
|
|
@ -547,12 +546,13 @@ func migrateSingleSessionToSQL(ctx context.Context, tx *s6.Queries,
|
|||
// into the linked child tables.
|
||||
if session.MacaroonRecipe != nil {
|
||||
// We start by inserting the macaroon permissions.
|
||||
for _, sessionPerm := range session.MacaroonRecipe.Permissions {
|
||||
for i, sessionPerm := range session.MacaroonRecipe.Permissions {
|
||||
err = tx.InsertSessionMacaroonPermission(
|
||||
ctx, s6.InsertSessionMacaroonPermissionParams{
|
||||
SessionID: sqlId,
|
||||
Entity: sessionPerm.Entity,
|
||||
Action: sessionPerm.Action,
|
||||
Position: int64(i),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -561,7 +561,7 @@ func migrateSingleSessionToSQL(ctx context.Context, tx *s6.Queries,
|
|||
}
|
||||
|
||||
// Next we insert the macaroon caveats.
|
||||
for _, caveat := range session.MacaroonRecipe.Caveats {
|
||||
for i, caveat := range session.MacaroonRecipe.Caveats {
|
||||
err = tx.InsertSessionMacaroonCaveat(
|
||||
ctx, s6.InsertSessionMacaroonCaveatParams{
|
||||
SessionID: sqlId,
|
||||
|
|
@ -570,6 +570,7 @@ func migrateSingleSessionToSQL(ctx context.Context, tx *s6.Queries,
|
|||
Location: sqldb.SQLStr(
|
||||
caveat.Location,
|
||||
),
|
||||
Position: int64(i),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -650,10 +651,6 @@ func overrideSessionTimeZone(session *Session) {
|
|||
// as nil in the bbolt store. Therefore, we also override the permissions
|
||||
// or caveats to nil for the migrated session in that scenario, so that the
|
||||
// deep equals check does not fail in this scenario either.
|
||||
//
|
||||
// Additionally, we sort the caveats & permissions of both the kv and sql
|
||||
// sessions by their ID, so that they are always comparable in a deterministic
|
||||
// way with deep equals.
|
||||
func overrideMacaroonRecipe(kvSession *Session, migratedSession *Session) {
|
||||
if kvSession.MacaroonRecipe != nil {
|
||||
kvPerms := kvSession.MacaroonRecipe.Permissions
|
||||
|
|
@ -671,30 +668,10 @@ func overrideMacaroonRecipe(kvSession *Session, migratedSession *Session) {
|
|||
}
|
||||
|
||||
sqlCaveats := migratedSession.MacaroonRecipe.Caveats
|
||||
sqlPerms := migratedSession.MacaroonRecipe.Permissions
|
||||
|
||||
// If there have been caveats set for the MacaroonRecipe,
|
||||
// the order of the postgres db caveats will in very rare cases
|
||||
// differ from the kv store caveats. Therefore, we sort
|
||||
// both the kv and sql caveats by their ID, so that we can
|
||||
// compare them in a deterministic way.
|
||||
if kvCaveats != nil {
|
||||
sort.Slice(kvCaveats, func(i, j int) bool {
|
||||
return bytes.Compare(
|
||||
kvCaveats[i].Id, kvCaveats[j].Id,
|
||||
) < 0
|
||||
})
|
||||
|
||||
sort.Slice(sqlCaveats, func(i, j int) bool {
|
||||
return bytes.Compare(
|
||||
sqlCaveats[i].Id, sqlCaveats[j].Id,
|
||||
) < 0
|
||||
})
|
||||
}
|
||||
|
||||
// Empty caveat verification IDs can be persisted as nil by SQL
|
||||
// backends, while the KV store can retain them as empty slices.
|
||||
// After sorting, we only normalize caveats that still line up
|
||||
// We only normalize caveats that still line up
|
||||
// by ID. If the lengths or IDs differ, we leave the slices
|
||||
// as-is and let the subsequent DeepEqual report the migration
|
||||
// mismatch, hence let the migration fail.
|
||||
|
|
@ -718,29 +695,6 @@ func overrideMacaroonRecipe(kvSession *Session, migratedSession *Session) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Similarly, we sort the macaroon permissions for both the kv
|
||||
// and sql sessions, so that we can compare them in a
|
||||
// deterministic way.
|
||||
if kvPerms != nil {
|
||||
sort.Slice(kvPerms, func(i, j int) bool {
|
||||
if kvPerms[i].Entity == kvPerms[j].Entity {
|
||||
return kvPerms[i].Action <
|
||||
kvPerms[j].Action
|
||||
}
|
||||
|
||||
return kvPerms[i].Entity < kvPerms[j].Entity
|
||||
})
|
||||
|
||||
sort.Slice(sqlPerms, func(i, j int) bool {
|
||||
if sqlPerms[i].Entity == sqlPerms[j].Entity {
|
||||
return sqlPerms[i].Action <
|
||||
sqlPerms[j].Action
|
||||
}
|
||||
|
||||
return sqlPerms[i].Entity < sqlPerms[j].Entity
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -240,13 +240,14 @@ func (s *SQLStore) NewSession(ctx context.Context, label string, typ Type,
|
|||
|
||||
// Write mac perms and caveats.
|
||||
if sess.MacaroonRecipe != nil {
|
||||
for _, perm := range sess.MacaroonRecipe.Permissions {
|
||||
for i, perm := range sess.MacaroonRecipe.Permissions {
|
||||
// nolint:ll
|
||||
err := db.InsertSessionMacaroonPermission(
|
||||
ctx, sqlc.InsertSessionMacaroonPermissionParams{
|
||||
SessionID: dbID,
|
||||
Entity: perm.Entity,
|
||||
Action: perm.Action,
|
||||
Position: int32(i),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
@ -255,7 +256,7 @@ func (s *SQLStore) NewSession(ctx context.Context, label string, typ Type,
|
|||
}
|
||||
}
|
||||
|
||||
for _, caveat := range sess.MacaroonRecipe.Caveats {
|
||||
for i, caveat := range sess.MacaroonRecipe.Caveats {
|
||||
// nolint:ll
|
||||
err := db.InsertSessionMacaroonCaveat(
|
||||
ctx, sqlc.InsertSessionMacaroonCaveatParams{
|
||||
|
|
@ -268,6 +269,7 @@ func (s *SQLStore) NewSession(ctx context.Context, label string, typ Type,
|
|||
Valid: caveat.
|
||||
Location != "",
|
||||
},
|
||||
Position: int32(i),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue