Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
K
keystore
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
id
keystore
Commits
38d0d11c
Commit
38d0d11c
authored
2 years ago
by
ale
Browse files
Options
Downloads
Patches
Plain Diff
Implement basic dump/restore functionality
parent
c4fe03f2
No related branches found
No related tags found
1 merge request
!35
Add state load/dump logic to the main server binary
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
server/keystore.go
+25
-1
25 additions, 1 deletion
server/keystore.go
server/keystore_test.go
+37
-0
37 additions, 0 deletions
server/keystore_test.go
server/map.go
+82
-39
82 additions, 39 deletions
server/map.go
with
144 additions
and
40 deletions
server/keystore.go
+
25
−
1
View file @
38d0d11c
package
server
package
server
import
(
import
(
"compress/gzip"
"context"
"context"
"errors"
"errors"
"io"
"io/ioutil"
"io/ioutil"
"log"
"log"
"strings"
"strings"
...
@@ -125,6 +127,28 @@ func (s *KeyStore) expireLoop() {
...
@@ -125,6 +127,28 @@ func (s *KeyStore) expireLoop() {
}
}
}
}
// Dump the keystore in-memory contents.
func
(
s
*
KeyStore
)
Dump
(
w
io
.
Writer
)
error
{
gzw
:=
gzip
.
NewWriter
(
w
)
if
err
:=
s
.
userKeys
.
dump
(
gzw
);
err
!=
nil
{
return
err
}
if
err
:=
gzw
.
Flush
();
err
!=
nil
{
return
err
}
return
gzw
.
Close
()
}
// Load a keystore data dump.
func
(
s
*
KeyStore
)
Load
(
r
io
.
Reader
)
error
{
gzr
,
err
:=
gzip
.
NewReader
(
r
)
if
err
!=
nil
{
return
err
}
defer
gzr
.
Close
()
return
s
.
userKeys
.
load
(
gzr
)
}
// Open the user's key store with the given password. If successful,
// Open the user's key store with the given password. If successful,
// the unencrypted user key will be stored for at most ttlSeconds, or
// the unencrypted user key will be stored for at most ttlSeconds, or
// until Close is called with the same session ID.
// until Close is called with the same session ID.
...
@@ -195,7 +219,7 @@ func (s *KeyStore) Get(username, ssoTicket string) ([]byte, error) {
...
@@ -195,7 +219,7 @@ func (s *KeyStore) Get(username, ssoTicket string) ([]byte, error) {
if
!
ok
{
if
!
ok
{
return
nil
,
errNoKeys
return
nil
,
errNoKeys
}
}
return
u
.
pk
ey
,
nil
return
u
.
PK
ey
,
nil
}
}
// Close the user's key store and wipe the associated unencrypted key
// Close the user's key store and wipe the associated unencrypted key
...
...
This diff is collapsed.
Click to expand it.
server/keystore_test.go
+
37
−
0
View file @
38d0d11c
...
@@ -283,3 +283,40 @@ func TestKeystore_Expire(t *testing.T) {
...
@@ -283,3 +283,40 @@ func TestKeystore_Expire(t *testing.T) {
t
.
Fatal
(
"keystore.Get():"
,
err
)
t
.
Fatal
(
"keystore.Get():"
,
err
)
}
}
}
}
func
TestKeystore_DumpAndReload
(
t
*
testing
.
T
)
{
_
,
keystore
,
cleanup
:=
newTestContext
(
t
)
defer
cleanup
()
// Decrypt the private key with the right password.
err
:=
keystore
.
Open
(
context
.
Background
(),
"testuser"
,
string
(
pw
),
"session"
,
60
)
if
err
!=
nil
{
t
.
Fatal
(
"keystore.Open():"
,
err
)
}
// Now dump the keystore state, and reload it in a new one.
var
buf
bytes
.
Buffer
if
err
:=
keystore
.
Dump
(
&
buf
);
err
!=
nil
{
t
.
Fatal
(
"keystore.Dump():"
,
err
)
}
c
,
keystore2
,
cleanup2
:=
newTestContext
(
t
)
defer
cleanup2
()
if
err
:=
keystore2
.
Load
(
&
buf
);
err
!=
nil
{
t
.
Fatal
(
"keystore2.Load():"
,
err
)
}
// Sign a valid SSO ticket and use it to obtain the private
// key we just stored.
ssoTicket
:=
c
.
sign
(
"testuser"
,
"keystore/"
,
"domain"
)
result
,
err
:=
keystore2
.
Get
(
"testuser"
,
ssoTicket
)
if
err
!=
nil
{
t
.
Fatal
(
"keystore2.Get():"
,
err
)
}
expectedPEM
,
_
:=
privKey
.
PEM
()
if
!
bytes
.
Equal
(
result
,
expectedPEM
)
{
t
.
Fatalf
(
"keystore2.Get() returned bad key: got %v, expected %v"
,
result
,
expectedPEM
)
}
}
This diff is collapsed.
Click to expand it.
server/map.go
+
82
−
39
View file @
38d0d11c
package
server
package
server
import
(
import
(
"bytes"
"container/heap"
"container/heap"
"encoding/gob"
"io"
"time"
"time"
)
)
type
userKey
struct
{
type
userKey
struct
{
pk
ey
[]
byte
PK
ey
[]
byte
}
}
func
newUserKey
(
pem
[]
byte
)
*
userKey
{
func
newUserKey
(
pem
[]
byte
)
*
userKey
{
return
&
userKey
{
pk
ey
:
pem
}
return
&
userKey
{
PK
ey
:
pem
}
}
}
type
userSession
struct
{
type
userSession
struct
{
id
string
ID
string
u
sername
string
U
sername
string
e
xpiry
time
.
Time
E
xpiry
time
.
Time
i
ndex
int
I
ndex
int
}
}
// Priority queue of userSession objects, kept ordered by expiration
// Priority queue of userSession objects, kept ordered by expiration
...
@@ -30,18 +33,18 @@ func (pq sessionPQ) Len() int {
...
@@ -30,18 +33,18 @@ func (pq sessionPQ) Len() int {
func
(
pq
sessionPQ
)
Swap
(
i
,
j
int
)
{
func
(
pq
sessionPQ
)
Swap
(
i
,
j
int
)
{
pq
[
i
],
pq
[
j
]
=
pq
[
j
],
pq
[
i
]
pq
[
i
],
pq
[
j
]
=
pq
[
j
],
pq
[
i
]
pq
[
i
]
.
i
ndex
=
i
pq
[
i
]
.
I
ndex
=
i
pq
[
j
]
.
i
ndex
=
j
pq
[
j
]
.
I
ndex
=
j
}
}
func
(
pq
sessionPQ
)
Less
(
i
,
j
int
)
bool
{
func
(
pq
sessionPQ
)
Less
(
i
,
j
int
)
bool
{
return
pq
[
i
]
.
e
xpiry
.
Before
(
pq
[
j
]
.
e
xpiry
)
return
pq
[
i
]
.
E
xpiry
.
Before
(
pq
[
j
]
.
E
xpiry
)
}
}
func
(
pq
*
sessionPQ
)
Push
(
x
interface
{})
{
func
(
pq
*
sessionPQ
)
Push
(
x
interface
{})
{
n
:=
len
(
*
pq
)
n
:=
len
(
*
pq
)
item
:=
x
.
(
*
userSession
)
item
:=
x
.
(
*
userSession
)
item
.
i
ndex
=
n
item
.
I
ndex
=
n
*
pq
=
append
(
*
pq
,
item
)
*
pq
=
append
(
*
pq
,
item
)
}
}
...
@@ -67,9 +70,9 @@ func newSessionMap() *sessionMap {
...
@@ -67,9 +70,9 @@ func newSessionMap() *sessionMap {
func
(
m
*
sessionMap
)
add
(
sessionID
,
username
string
,
ttl
time
.
Duration
)
{
func
(
m
*
sessionMap
)
add
(
sessionID
,
username
string
,
ttl
time
.
Duration
)
{
sess
:=
&
userSession
{
sess
:=
&
userSession
{
id
:
sessionID
,
ID
:
sessionID
,
u
sername
:
username
,
U
sername
:
username
,
e
xpiry
:
time
.
Now
()
.
Add
(
ttl
),
E
xpiry
:
time
.
Now
()
.
Add
(
ttl
),
}
}
m
.
sessions
[
sessionID
]
=
sess
m
.
sessions
[
sessionID
]
=
sess
heap
.
Push
(
&
m
.
pq
,
sess
)
heap
.
Push
(
&
m
.
pq
,
sess
)
...
@@ -81,60 +84,82 @@ func (m *sessionMap) del(sessionID string) *userSession {
...
@@ -81,60 +84,82 @@ func (m *sessionMap) del(sessionID string) *userSession {
return
nil
return
nil
}
}
delete
(
m
.
sessions
,
sessionID
)
delete
(
m
.
sessions
,
sessionID
)
heap
.
Remove
(
&
m
.
pq
,
sess
.
i
ndex
)
heap
.
Remove
(
&
m
.
pq
,
sess
.
I
ndex
)
sess
.
i
ndex
=
-
1
sess
.
I
ndex
=
-
1
return
sess
return
sess
}
}
// Peek at the oldest entry and pop it if expired.
// Peek at the oldest entry and pop it if expired.
func
(
m
*
sessionMap
)
expireNext
(
deadline
time
.
Time
)
*
userSession
{
func
(
m
*
sessionMap
)
expireNext
(
deadline
time
.
Time
)
*
userSession
{
if
len
(
m
.
pq
)
>
0
&&
m
.
pq
[
0
]
.
e
xpiry
.
Before
(
deadline
)
{
if
len
(
m
.
pq
)
>
0
&&
m
.
pq
[
0
]
.
E
xpiry
.
Before
(
deadline
)
{
sess
:=
heap
.
Pop
(
&
m
.
pq
)
.
(
*
userSession
)
sess
:=
heap
.
Pop
(
&
m
.
pq
)
.
(
*
userSession
)
delete
(
m
.
sessions
,
sess
.
id
)
delete
(
m
.
sessions
,
sess
.
ID
)
sess
.
i
ndex
=
-
1
sess
.
I
ndex
=
-
1
return
sess
return
sess
}
}
return
nil
return
nil
}
}
func
(
m
*
sessionMap
)
GobEncode
()
([]
byte
,
error
)
{
var
buf
bytes
.
Buffer
if
err
:=
gob
.
NewEncoder
(
&
buf
)
.
Encode
([]
*
userSession
(
m
.
pq
));
err
!=
nil
{
return
nil
,
err
}
return
buf
.
Bytes
(),
nil
}
func
(
m
*
sessionMap
)
GobDecode
(
b
[]
byte
)
error
{
var
tmp
[]
*
userSession
if
err
:=
gob
.
NewDecoder
(
bytes
.
NewReader
(
b
))
.
Decode
(
&
tmp
);
err
!=
nil
{
return
err
}
sessions
:=
make
(
map
[
string
]
*
userSession
)
for
_
,
sess
:=
range
tmp
{
sessions
[
sess
.
ID
]
=
sess
}
m
.
pq
=
sessionPQ
(
tmp
)
m
.
sessions
=
sessions
return
nil
}
// Maintain association between sessions (that may expire) and user
// Maintain association between sessions (that may expire) and user
// keys. Enforces the constraint that keys will be removed once they
// keys. Enforces the constraint that keys will be removed once they
// have no sessions attached.
// have no sessions attached.
type
userKeyMap
struct
{
type
userKeyMap
struct
{
s
essions
*
sessionMap
S
essions
*
sessionMap
u
serKeys
map
[
string
]
*
userKey
U
serKeys
map
[
string
]
*
userKey
u
serSessions
map
[
string
][]
string
U
serSessions
map
[
string
][]
string
}
}
func
newUserKeyMap
()
*
userKeyMap
{
func
newUserKeyMap
()
*
userKeyMap
{
return
&
userKeyMap
{
return
&
userKeyMap
{
s
essions
:
newSessionMap
(),
S
essions
:
newSessionMap
(),
u
serKeys
:
make
(
map
[
string
]
*
userKey
),
U
serKeys
:
make
(
map
[
string
]
*
userKey
),
u
serSessions
:
make
(
map
[
string
][]
string
),
U
serSessions
:
make
(
map
[
string
][]
string
),
}
}
}
}
func
(
u
*
userKeyMap
)
numCachedKeys
()
int
{
func
(
u
*
userKeyMap
)
numCachedKeys
()
int
{
return
len
(
u
.
u
serKeys
)
return
len
(
u
.
U
serKeys
)
}
}
func
(
u
*
userKeyMap
)
numSessions
()
int
{
func
(
u
*
userKeyMap
)
numSessions
()
int
{
return
u
.
s
essions
.
pq
.
Len
()
return
u
.
S
essions
.
pq
.
Len
()
}
}
func
(
u
*
userKeyMap
)
get
(
username
string
)
(
*
userKey
,
bool
)
{
func
(
u
*
userKeyMap
)
get
(
username
string
)
(
*
userKey
,
bool
)
{
k
,
ok
:=
u
.
u
serKeys
[
username
]
k
,
ok
:=
u
.
U
serKeys
[
username
]
return
k
,
ok
return
k
,
ok
}
}
func
(
u
*
userKeyMap
)
addSessionWithKey
(
sessionID
,
username
string
,
key
*
userKey
,
ttlSeconds
int
)
{
func
(
u
*
userKeyMap
)
addSessionWithKey
(
sessionID
,
username
string
,
key
*
userKey
,
ttlSeconds
int
)
{
u
.
s
essions
.
add
(
sessionID
,
username
,
time
.
Duration
(
ttlSeconds
)
*
time
.
Second
)
u
.
S
essions
.
add
(
sessionID
,
username
,
time
.
Duration
(
ttlSeconds
)
*
time
.
Second
)
u
.
u
serKeys
[
username
]
=
key
u
.
U
serKeys
[
username
]
=
key
u
.
u
serSessions
[
username
]
=
append
(
u
.
u
serSessions
[
username
],
sessionID
)
u
.
U
serSessions
[
username
]
=
append
(
u
.
U
serSessions
[
username
],
sessionID
)
}
}
func
(
u
*
userKeyMap
)
deleteSession
(
sessionID
string
)
bool
{
func
(
u
*
userKeyMap
)
deleteSession
(
sessionID
string
)
bool
{
if
sess
:=
u
.
s
essions
.
del
(
sessionID
);
sess
!=
nil
{
if
sess
:=
u
.
S
essions
.
del
(
sessionID
);
sess
!=
nil
{
u
.
cleanupSession
(
sess
)
u
.
cleanupSession
(
sess
)
return
true
return
true
}
}
...
@@ -143,7 +168,7 @@ func (u *userKeyMap) deleteSession(sessionID string) bool {
...
@@ -143,7 +168,7 @@ func (u *userKeyMap) deleteSession(sessionID string) bool {
func
(
u
*
userKeyMap
)
expire
(
deadline
time
.
Time
)
{
func
(
u
*
userKeyMap
)
expire
(
deadline
time
.
Time
)
{
for
{
for
{
sess
:=
u
.
s
essions
.
expireNext
(
deadline
)
sess
:=
u
.
S
essions
.
expireNext
(
deadline
)
if
sess
==
nil
{
if
sess
==
nil
{
return
return
}
}
...
@@ -153,21 +178,29 @@ func (u *userKeyMap) expire(deadline time.Time) {
...
@@ -153,21 +178,29 @@ func (u *userKeyMap) expire(deadline time.Time) {
func
(
u
*
userKeyMap
)
cleanupSession
(
sess
*
userSession
)
{
func
(
u
*
userKeyMap
)
cleanupSession
(
sess
*
userSession
)
{
var
ids
[]
string
var
ids
[]
string
for
_
,
id
:=
range
u
.
u
serSessions
[
sess
.
u
sername
]
{
for
_
,
id
:=
range
u
.
U
serSessions
[
sess
.
U
sername
]
{
if
id
!=
sess
.
id
{
if
id
!=
sess
.
ID
{
ids
=
append
(
ids
,
id
)
ids
=
append
(
ids
,
id
)
}
}
}
}
if
len
(
ids
)
==
0
{
if
len
(
ids
)
==
0
{
// No more sessions for this user, delete key.
// No more sessions for this user, delete key.
k
:=
u
.
u
serKeys
[
sess
.
u
sername
]
k
:=
u
.
U
serKeys
[
sess
.
U
sername
]
delete
(
u
.
u
serKeys
,
sess
.
u
sername
)
delete
(
u
.
U
serKeys
,
sess
.
U
sername
)
wipeBytes
(
k
.
pk
ey
)
wipeBytes
(
k
.
PK
ey
)
delete
(
u
.
u
serSessions
,
sess
.
u
sername
)
delete
(
u
.
U
serSessions
,
sess
.
U
sername
)
}
else
{
}
else
{
u
.
userSessions
[
sess
.
username
]
=
ids
u
.
UserSessions
[
sess
.
Username
]
=
ids
}
}
}
func
(
u
*
userKeyMap
)
dump
(
w
io
.
Writer
)
error
{
return
gob
.
NewEncoder
(
w
)
.
Encode
(
u
)
}
func
(
u
*
userKeyMap
)
load
(
r
io
.
Reader
)
error
{
return
gob
.
NewDecoder
(
r
)
.
Decode
(
u
)
}
}
func
wipeBytes
(
b
[]
byte
)
{
func
wipeBytes
(
b
[]
byte
)
{
...
@@ -175,3 +208,13 @@ func wipeBytes(b []byte) {
...
@@ -175,3 +208,13 @@ func wipeBytes(b []byte) {
b
[
i
]
=
0
b
[
i
]
=
0
}
}
}
}
func
init
()
{
// Register the types referenced by userKeyMap with the gob
// encoder. Use short names so that maps of objects take less
// space in the output.
gob
.
RegisterName
(
"k"
,
&
userKey
{})
gob
.
RegisterName
(
"s"
,
&
userSession
{})
gob
.
RegisterName
(
"m"
,
&
sessionMap
{})
gob
.
RegisterName
(
"ukm"
,
&
userKeyMap
{})
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment