mirror of
https://github.com/postmannen/ctrl.git
synced 2025-01-05 20:09:16 +00:00
initial acl distribution works
This commit is contained in:
parent
e7ae3d893b
commit
9d71f11145
4 changed files with 122 additions and 15 deletions
|
@ -4,8 +4,10 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -42,13 +44,17 @@ type accessLists struct {
|
||||||
|
|
||||||
func newAccessLists(errorKernel *errorKernel, configuration *Configuration) *accessLists {
|
func newAccessLists(errorKernel *errorKernel, configuration *Configuration) *accessLists {
|
||||||
a := accessLists{
|
a := accessLists{
|
||||||
schemaMain: newSchemaMain(),
|
schemaMain: newSchemaMain(configuration),
|
||||||
schemaGenerated: newSchemaGenerated(),
|
schemaGenerated: newSchemaGenerated(),
|
||||||
validator: validator.New(),
|
validator: validator.New(),
|
||||||
errorKernel: errorKernel,
|
errorKernel: errorKernel,
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The main acl map gets loaded from disk in the newSchemaMain function, but since that
|
||||||
|
// function do not have access to the generated map we have to generate it here.
|
||||||
|
a.generateACLsForAllNodes()
|
||||||
|
|
||||||
return &a
|
return &a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,17 +66,52 @@ type commandGroup string
|
||||||
// schemaMain is the structure that holds the user editable parts for creating ACL's.
|
// schemaMain is the structure that holds the user editable parts for creating ACL's.
|
||||||
type schemaMain struct {
|
type schemaMain struct {
|
||||||
ACLMap map[Node]map[Node]map[command]struct{}
|
ACLMap map[Node]map[Node]map[command]struct{}
|
||||||
|
ACLMapFilePath string
|
||||||
NodeGroupMap map[nodeGroup]map[Node]struct{}
|
NodeGroupMap map[nodeGroup]map[Node]struct{}
|
||||||
CommandGroupMap map[commandGroup]map[command]struct{}
|
CommandGroupMap map[commandGroup]map[command]struct{}
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSchemaMain() *schemaMain {
|
func newSchemaMain(configuration *Configuration) *schemaMain {
|
||||||
s := schemaMain{
|
s := schemaMain{
|
||||||
ACLMap: make(map[Node]map[Node]map[command]struct{}),
|
ACLMap: make(map[Node]map[Node]map[command]struct{}),
|
||||||
|
ACLMapFilePath: filepath.Join(configuration.DatabaseFolder, "central_aclmap.txt"),
|
||||||
NodeGroupMap: make(map[nodeGroup]map[Node]struct{}),
|
NodeGroupMap: make(map[nodeGroup]map[Node]struct{}),
|
||||||
CommandGroupMap: make(map[commandGroup]map[command]struct{}),
|
CommandGroupMap: make(map[commandGroup]map[command]struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load ACLMap from disk if present.
|
||||||
|
func() {
|
||||||
|
if _, err := os.Stat(s.ACLMapFilePath); os.IsNotExist(err) {
|
||||||
|
er := fmt.Errorf("error: newSchemaMain: no file for ACLMap found %v: %v", s.ACLMapFilePath, err)
|
||||||
|
log.Printf("%v\n", er)
|
||||||
|
|
||||||
|
// If no aclmap is present on disk we just return from this
|
||||||
|
// function without loading any values.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, err := os.Open(s.ACLMapFilePath)
|
||||||
|
if err != nil {
|
||||||
|
er := fmt.Errorf("error: newSchemaMain: failed to open file for reading %v: %v", s.ACLMapFilePath, err)
|
||||||
|
log.Printf("%v\n", er)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := io.ReadAll(fh)
|
||||||
|
if err != nil {
|
||||||
|
er := fmt.Errorf("error: newSchemaMain: failed to ReadAll file %v: %v", s.ACLMapFilePath, err)
|
||||||
|
log.Printf("%v\n", er)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the data read from disk.
|
||||||
|
err = json.Unmarshal(b, &s.ACLMap)
|
||||||
|
if err != nil {
|
||||||
|
er := fmt.Errorf("error: newSchemaMain: failed to unmarshal content from file %v: %v", s.ACLMapFilePath, err)
|
||||||
|
log.Printf("%v\n", er)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the aclGenerated map happens in the function where this function is called.
|
||||||
|
}()
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +283,27 @@ func (a *accessLists) aclDeleteSource(host Node, source Node) error {
|
||||||
// nodes.
|
// nodes.
|
||||||
// The result will be written to the schemaGenerated.ACLsToConvert map.
|
// The result will be written to the schemaGenerated.ACLsToConvert map.
|
||||||
func (a *accessLists) generateACLsForAllNodes() error {
|
func (a *accessLists) generateACLsForAllNodes() error {
|
||||||
|
// We first one to save the current main ACLMap.
|
||||||
|
func() {
|
||||||
|
fh, err := os.OpenFile(a.schemaMain.ACLMapFilePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600)
|
||||||
|
if err != nil {
|
||||||
|
er := fmt.Errorf("error: generateACLsForAllNodes: opening file for writing: %v, err: %v", a.schemaMain.ACLMapFilePath, err)
|
||||||
|
log.Printf("%v\n", er)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
// a.schemaMain.mu.Lock()
|
||||||
|
// defer a.schemaMain.mu.Unlock()
|
||||||
|
enc := json.NewEncoder(fh)
|
||||||
|
enc.Encode(a.schemaMain.ACLMap)
|
||||||
|
if err != nil {
|
||||||
|
er := fmt.Errorf("error: generateACLsForAllNodes: encoding json to file failed: %v, err: %v", a.schemaMain.ACLMapFilePath, err)
|
||||||
|
log.Printf("%v\n", er)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
a.schemaGenerated.mu.Lock()
|
a.schemaGenerated.mu.Lock()
|
||||||
defer a.schemaGenerated.mu.Unlock()
|
defer a.schemaGenerated.mu.Unlock()
|
||||||
|
|
||||||
|
|
12
processes.go
12
processes.go
|
@ -178,6 +178,7 @@ func (p *processes) Start(proc process) {
|
||||||
// TODO: Putting the acl publisher here.
|
// TODO: Putting the acl publisher here.
|
||||||
// Maybe we should also change the name of the configuration flag to something auth related ?
|
// Maybe we should also change the name of the configuration flag to something auth related ?
|
||||||
proc.startup.pubREQAclRequestUpdate(proc)
|
proc.startup.pubREQAclRequestUpdate(proc)
|
||||||
|
proc.startup.subREQAclDeliverUpdate(proc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if proc.configuration.IsCentralAuth {
|
if proc.configuration.IsCentralAuth {
|
||||||
|
@ -342,7 +343,7 @@ func (s startup) pubREQKeysRequestUpdate(p process) {
|
||||||
// and update with new keys back.
|
// and update with new keys back.
|
||||||
|
|
||||||
proc.nodeAuth.publicKeys.mu.Lock()
|
proc.nodeAuth.publicKeys.mu.Lock()
|
||||||
fmt.Printf("\n ----> REQKeysRequestUpdate: sending our current hash: %v\n\n", []byte(proc.nodeAuth.publicKeys.keysAndHash.Hash[:]))
|
fmt.Printf("\n ----> publisher REQKeysRequestUpdate: sending our current hash: %v\n\n", []byte(proc.nodeAuth.publicKeys.keysAndHash.Hash[:]))
|
||||||
|
|
||||||
m := Message{
|
m := Message{
|
||||||
FileName: "publickeysget.log",
|
FileName: "publickeysget.log",
|
||||||
|
@ -397,7 +398,7 @@ func (s startup) pubREQAclRequestUpdate(p process) {
|
||||||
// and update with new keys back.
|
// and update with new keys back.
|
||||||
|
|
||||||
proc.nodeAuth.nodeAcl.mu.Lock()
|
proc.nodeAuth.nodeAcl.mu.Lock()
|
||||||
fmt.Printf("\n ----> REQKeysRequestUpdate: sending our current hash: %v\n\n", []byte(proc.nodeAuth.nodeAcl.aclAndHash.Hash[:]))
|
fmt.Printf("\n ----> publisher REQAclRequestUpdate: sending our current hash: %v\n\n", []byte(proc.nodeAuth.nodeAcl.aclAndHash.Hash[:]))
|
||||||
|
|
||||||
m := Message{
|
m := Message{
|
||||||
FileName: "aclRequestUpdate.log",
|
FileName: "aclRequestUpdate.log",
|
||||||
|
@ -461,6 +462,13 @@ func (s startup) subREQAclRequestUpdate(p process) {
|
||||||
go proc.spawnWorker()
|
go proc.spawnWorker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s startup) subREQAclDeliverUpdate(p process) {
|
||||||
|
log.Printf("Starting Acl deliver update subscriber: %#v\n", p.node)
|
||||||
|
sub := newSubject(REQAclDeliverUpdate, string(p.node))
|
||||||
|
proc := newProcess(p.ctx, s.server, sub, processKindSubscriber, nil)
|
||||||
|
go proc.spawnWorker()
|
||||||
|
}
|
||||||
|
|
||||||
// HERE!
|
// HERE!
|
||||||
|
|
||||||
func (s startup) subREQAclAddCommand(p process) {
|
func (s startup) subREQAclAddCommand(p process) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/fxamacker/cbor/v2"
|
"github.com/fxamacker/cbor/v2"
|
||||||
)
|
)
|
||||||
|
@ -20,6 +21,11 @@ func (m methodREQAclRequestUpdate) getKind() Event {
|
||||||
|
|
||||||
// Handler to get all acl's from a central server.
|
// Handler to get all acl's from a central server.
|
||||||
func (m methodREQAclRequestUpdate) handler(proc process, message Message, node string) ([]byte, error) {
|
func (m methodREQAclRequestUpdate) handler(proc process, message Message, node string) ([]byte, error) {
|
||||||
|
inf := fmt.Errorf("<--- subscriber methodREQAclRequestUpdate received from: %v, and the data which is the nodes current acl hash containing: %v", message.FromNode, message.MethodArgs)
|
||||||
|
proc.errorKernel.logConsoleOnlyIfDebug(inf, proc.configuration)
|
||||||
|
|
||||||
|
fmt.Printf("\n --- subscriber methodREQAclRequestUpdate: the message brought to handler : %+v\n", message)
|
||||||
|
|
||||||
// Get a context with the timeout specified in message.MethodTimeout.
|
// Get a context with the timeout specified in message.MethodTimeout.
|
||||||
ctx, _ := getContextForMethodTimeout(proc.ctx, message)
|
ctx, _ := getContextForMethodTimeout(proc.ctx, message)
|
||||||
|
|
||||||
|
@ -47,27 +53,47 @@ func (m methodREQAclRequestUpdate) handler(proc process, message Message, node s
|
||||||
proc.centralAuth.accessLists.schemaGenerated.mu.Lock()
|
proc.centralAuth.accessLists.schemaGenerated.mu.Lock()
|
||||||
defer proc.centralAuth.accessLists.schemaGenerated.mu.Unlock()
|
defer proc.centralAuth.accessLists.schemaGenerated.mu.Unlock()
|
||||||
|
|
||||||
fmt.Printf(" <---- methodREQAclRequestUpdate: received acl hash from NODE=%v, HASH=%v\n", message.FromNode, message.Data)
|
// DEBUGGING:
|
||||||
|
{
|
||||||
|
proc.centralAuth.accessLists.schemaMain.mu.Lock()
|
||||||
|
fmt.Printf("\n --- DEBUGGING: subscriber methodREQAclRequestUpdate: schemaGenerated contains: %v\n\n", proc.centralAuth.accessLists.schemaGenerated)
|
||||||
|
fmt.Printf("\n --- DEBUGGING: subscriber methodREQAclRequestUpdate: schemaMain contains: %v\n\n", proc.centralAuth.accessLists.schemaMain)
|
||||||
|
proc.centralAuth.accessLists.schemaMain.mu.Unlock()
|
||||||
|
|
||||||
|
// TODO: PROBLEM: The existing generated acl's are not loaded when starting, or not stored at all.
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" ---- subscriber methodREQAclRequestUpdate: got acl hash from NODE=%v, HASH=%v\n", message.FromNode, message.Data)
|
||||||
|
|
||||||
// Check if the received hash is the same as the one currently active,
|
// Check if the received hash is the same as the one currently active,
|
||||||
// If it is the same we exit the handler immediately.
|
// If it is the same we exit the handler immediately.
|
||||||
hash32 := proc.centralAuth.accessLists.schemaGenerated.GeneratedACLsMap[message.FromNode].Hash
|
hash32 := proc.centralAuth.accessLists.schemaGenerated.GeneratedACLsMap[message.FromNode].Hash
|
||||||
hash := hash32[:]
|
hash := hash32[:]
|
||||||
|
fmt.Printf("\n ---- subscriber methodREQAclRequestUpdate: on central hash32=%v\n\n", hash32)
|
||||||
if bytes.Equal(hash, message.Data) {
|
if bytes.Equal(hash, message.Data) {
|
||||||
fmt.Printf("\n ------------ NODE AND CENTRAL HAVE EQUAL ACL HASH, NOTHING TO DO, EXITING HANDLER\n\n")
|
fmt.Printf("\n ---- subscriber methodREQAclRequestUpdate: NODE AND CENTRAL HAVE EQUAL ACL HASH, NOTHING TO DO, EXITING HANDLER\n\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n ------------ NODE AND CENTRAL HAD NOT EQUAL ACL, PREPARING TO SEND NEW VERSION OF Acl\n\n")
|
fmt.Printf("\n ---- subscriber methodREQAclRequestUpdate: NODE AND CENTRAL HAD NOT EQUAL ACL, PREPARING TO SEND NEW VERSION OF Acl\n\n")
|
||||||
|
|
||||||
fmt.Printf("\n ----> methodREQAclRequestUpdate: SENDING ACL'S TO NODE=%v\n", message.FromNode)
|
// Generate JSON for Message.Data
|
||||||
|
|
||||||
js, err := json.Marshal(proc.centralAuth.accessLists.schemaGenerated.GeneratedACLsMap[message.FromNode])
|
hdh := HostACLsSerializedWithHash{}
|
||||||
|
hdh.Data = proc.centralAuth.accessLists.schemaGenerated.GeneratedACLsMap[message.FromNode].Data
|
||||||
|
fmt.Printf("\n * DEBUGGING: before marshalling, hdh.Data=%v\n", hdh.Data)
|
||||||
|
hdh.Hash = proc.centralAuth.accessLists.schemaGenerated.GeneratedACLsMap[message.FromNode].Hash
|
||||||
|
fmt.Printf("\n * DEBUGGING: before marshalling, hdh.Hash=%v\n\n", hdh.Hash)
|
||||||
|
|
||||||
|
js, err := json.Marshal(hdh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
er := fmt.Errorf("error: REQAclRequestUpdate : json marshal failed: %v, message: %v", err, message)
|
er := fmt.Errorf("error: REQAclRequestUpdate : json marshal failed: %v, message: %v", err, message)
|
||||||
proc.errorKernel.errSend(proc, message, er)
|
proc.errorKernel.errSend(proc, message, er)
|
||||||
|
log.Fatalf("%v\n", er)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n ----> subscriber methodREQAclRequestUpdate: SENDING ACL'S TO NODE=%v, serializedAndHash=%+v\n", message.FromNode, hdh)
|
||||||
|
|
||||||
newReplyMessage(proc, message, js)
|
newReplyMessage(proc, message, js)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -89,6 +115,11 @@ func (m methodREQAclDeliverUpdate) getKind() Event {
|
||||||
|
|
||||||
// Handler to receive the acls from a central server.
|
// Handler to receive the acls from a central server.
|
||||||
func (m methodREQAclDeliverUpdate) handler(proc process, message Message, node string) ([]byte, error) {
|
func (m methodREQAclDeliverUpdate) handler(proc process, message Message, node string) ([]byte, error) {
|
||||||
|
inf := fmt.Errorf("<--- subscriber methodREQAclDeliverUpdate received from: %v, containing: %v", message.FromNode, message.Data)
|
||||||
|
proc.errorKernel.logConsoleOnlyIfDebug(inf, proc.configuration)
|
||||||
|
|
||||||
|
fmt.Printf("\n --- subscriber methodREQAclRequestUpdate: the message received on handler : %+v\n\n", message)
|
||||||
|
|
||||||
// Get a context with the timeout specified in message.MethodTimeout.
|
// Get a context with the timeout specified in message.MethodTimeout.
|
||||||
ctx, _ := getContextForMethodTimeout(proc.ctx, message)
|
ctx, _ := getContextForMethodTimeout(proc.ctx, message)
|
||||||
|
|
||||||
|
@ -120,27 +151,30 @@ func (m methodREQAclDeliverUpdate) handler(proc process, message Message, node s
|
||||||
|
|
||||||
err := json.Unmarshal(message.Data, &hdh)
|
err := json.Unmarshal(message.Data, &hdh)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
er := fmt.Errorf("error: REQAclDeliverUpdate : json unmarshal failed: %v, message: %v", err, message)
|
er := fmt.Errorf("error: subscriber REQAclDeliverUpdate : json unmarshal failed: %v, message: %v", err, message)
|
||||||
proc.errorKernel.errSend(proc, message, er)
|
proc.errorKernel.errSend(proc, message, er)
|
||||||
|
log.Fatalf("\n * DEBUG: ER: %v\n", er)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapOfFromNodeCommands := make(map[Node]map[command]struct{})
|
mapOfFromNodeCommands := make(map[Node]map[command]struct{})
|
||||||
err = cbor.Unmarshal(hdh.Data, &mapOfFromNodeCommands)
|
err = cbor.Unmarshal(hdh.Data, &mapOfFromNodeCommands)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
er := fmt.Errorf("error: REQAclDeliverUpdate : json unmarshal failed: %v, message: %v", err, message)
|
er := fmt.Errorf("error: subscriber REQAclDeliverUpdate : json unmarshal failed: %v, message: %v", err, message)
|
||||||
proc.errorKernel.errSend(proc, message, er)
|
proc.errorKernel.errSend(proc, message, er)
|
||||||
|
log.Fatalf("\n * DEBUG: ER: %v\n", er)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proc.nodeAuth.nodeAcl.aclAndHash.Hash = hdh.Hash
|
proc.nodeAuth.nodeAcl.aclAndHash.Hash = hdh.Hash
|
||||||
proc.nodeAuth.nodeAcl.aclAndHash.Acl = mapOfFromNodeCommands
|
proc.nodeAuth.nodeAcl.aclAndHash.Acl = mapOfFromNodeCommands
|
||||||
|
|
||||||
fmt.Printf("\n <---- REQAclDeliverUpdate: after unmarshal, nodeAuth aclAndhash contains: %+v\n\n", proc.nodeAuth.nodeAcl.aclAndHash)
|
fmt.Printf("\n <---- subscriber REQAclDeliverUpdate: after unmarshal, nodeAuth aclAndhash contains: %+v\n\n", proc.nodeAuth.nodeAcl.aclAndHash)
|
||||||
|
|
||||||
proc.nodeAuth.nodeAcl.mu.Unlock()
|
proc.nodeAuth.nodeAcl.mu.Unlock()
|
||||||
|
|
||||||
err = proc.nodeAuth.nodeAcl.saveToFile()
|
err = proc.nodeAuth.nodeAcl.saveToFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
er := fmt.Errorf("error: REQAclDeliverUpdate : save to file failed: %v, message: %v", err, message)
|
er := fmt.Errorf("error: subscriber REQAclDeliverUpdate : save to file failed: %v, message: %v", err, message)
|
||||||
proc.errorKernel.errSend(proc, message, er)
|
proc.errorKernel.errSend(proc, message, er)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,6 +876,9 @@ func (m methodREQAclExport) handler(proc process, message Message, node string)
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
|
// TODO: IMPORTANT: We also need to add importing and exporting of the groups to the
|
||||||
|
// import and export methods.
|
||||||
|
|
||||||
type methodREQAclImport struct {
|
type methodREQAclImport struct {
|
||||||
event Event
|
event Event
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,11 +117,11 @@ func (m methodREQKeysRequestUpdate) handler(proc process, message Message, node
|
||||||
|
|
||||||
// Check if the received hash is the same as the one currently active,
|
// Check if the received hash is the same as the one currently active,
|
||||||
if bytes.Equal(proc.centralAuth.pki.nodesAcked.keysAndHash.Hash[:], message.Data) {
|
if bytes.Equal(proc.centralAuth.pki.nodesAcked.keysAndHash.Hash[:], message.Data) {
|
||||||
fmt.Printf("\n ------------ NODE AND CENTRAL HAVE EQUAL KEYS, NOTHING TO DO, EXITING HANDLER\n\n")
|
fmt.Printf("\n --- methodREQKeysRequestUpdate: NODE AND CENTRAL HAVE EQUAL KEYS, NOTHING TO DO, EXITING HANDLER\n\n")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n ------------ NODE AND CENTRAL HAD NOT EQUAL KEYS, PREPARING TO SEND NEW VERSION OF KEYS\n\n")
|
fmt.Printf("\n ------------methodREQKeysRequestUpdate: NODE AND CENTRAL HAD NOT EQUAL KEYS, PREPARING TO SEND NEW VERSION OF KEYS\n\n")
|
||||||
|
|
||||||
fmt.Printf(" * methodREQKeysRequestUpdate: marshalling new keys and hash to send: map=%v, hash=%v\n\n", proc.centralAuth.pki.nodesAcked.keysAndHash.Keys, proc.centralAuth.pki.nodesAcked.keysAndHash.Hash)
|
fmt.Printf(" * methodREQKeysRequestUpdate: marshalling new keys and hash to send: map=%v, hash=%v\n\n", proc.centralAuth.pki.nodesAcked.keysAndHash.Keys, proc.centralAuth.pki.nodesAcked.keysAndHash.Hash)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue