Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/getting-started/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ A Web restart, including one caused by `oac apply`, signs everyone out of the co

## Core key

Each installation has one administrator credential, the Core key. The installer generates a 64-character random key in `data/secrets/web/core.key`. Read it with `oac core-key --show`; the file is owned by the container user. The Core key:
Each installation has one administrator credential, the Core key. The installer generates a key with the `oac_admin_` prefix followed by 64 random lowercase hexadecimal characters in `data/secrets/web/core.key`. Read it with `oac core-key --show`; the file is owned by the container user. The Core key:

- signs in to Web. The browser gets an HttpOnly session cookie, never the key;
- authorizes Core API (`/core/v1`) requests sent as `Authorization: Bearer <Core key>`;
Expand Down
4 changes: 2 additions & 2 deletions docs/zh/getting-started/operations.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "管理你的安装"
source: docs/getting-started/operations.md
source_hash: e87c7a1e577b8732929bb1c1fead1d54abfe6b607d8c4aed43faf38dca405ed5
source_hash: e60a6e96cd61692b6adc7664874ba0ed2ce489982e7da2fe2347631ee2a94c0a
---

安装运维人员负责 Core 主机、存储和可用性。节点主机运行各自的服务;参阅[节点](nodes.md)。设置见[配置参考](../configuration.md)。
Expand Down Expand Up @@ -62,7 +62,7 @@ Web 重启(包括 `oac apply` 引起的重启)会让所有控制台用户退

## Core 密钥 {#core-key}

每个安装有一个管理员凭据,即 Core 密钥。安装程序在 `data/secrets/web/core.key` 生成 64 字符随机密钥。用 `oac core-key --show` 读取;该文件属于容器用户。Core 密钥:
每个安装有一个管理员凭据,即 Core 密钥。安装程序在 `data/secrets/web/core.key` 生成以 `oac_admin_` 为前缀、后接 64 个随机小写十六进制字符的密钥。用 `oac core-key --show` 读取;该文件属于容器用户。Core 密钥:

- 用于登录 Web。浏览器获得 HttpOnly 会话 cookie,不持有密钥;
- 通过 `Authorization: Bearer <Core key>` 授权 Core API(`/core/v1`)请求;
Expand Down
8 changes: 7 additions & 1 deletion services/core/cmd/oac/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,13 @@ func initialize(root string, release releaseIdentity, fetch func() (map[string][
name string
generate func() string
}{
{"secrets/web/core.key", func() string { return randomHex(32) }},
{"secrets/web/core.key", func() string {
key, err := generateCoreKey()
if err != nil {
panic(err)
}
return key
}},
{"secrets/database/password", func() string { return randomHex(32) }},
{"secrets/core/credential.key", func() string { return base64.StdEncoding.EncodeToString(randomBytes(32)) }},
{"secrets/core/installation.id", func() string { return uuid.NewString() }},
Expand Down
4 changes: 1 addition & 3 deletions services/core/cmd/oac/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ func TestInitializeKeepsIdentityAndKeysAcrossRestarts(t *testing.T) {
}
saved := snapshot(t, root)
key := strings.TrimSpace(saved["secrets/web/core.key"])
if len(key) != 64 {
t.Fatalf("core key has %d characters", len(key))
}
assertCoreKeyFormat(t, key)
var digests []string
if err := json.Unmarshal([]byte(saved["secrets/core/core-key-digests.json"]), &digests); err != nil || len(digests) != 1 || digests[0] != keyDigest(key) {
t.Fatalf("digests = %v, %v", digests, err)
Expand Down
11 changes: 9 additions & 2 deletions services/core/cmd/oac/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,19 @@ func coreKeyCommand(ctx context.Context, in installation, runner Runner, args []
return errors.New("Usage: oac core-key [--show]")
}

func rotateCoreKey(ctx context.Context, in installation, runner Runner) error {
func generateCoreKey() (string, error) {
buf := make([]byte, 32)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return "oac_admin_" + hex.EncodeToString(buf), nil
}

func rotateCoreKey(ctx context.Context, in installation, runner Runner) error {
key, err := generateCoreKey()
if err != nil {
return err
}
key := hex.EncodeToString(buf)
keyPath := filepath.Join(in.data, "secrets", "web", "core.key")
digestPath := filepath.Join(in.data, "secrets", "core", "core-key-digests.json")
if err := writeSecret(keyPath, key+"\n"); err != nil {
Expand Down
9 changes: 9 additions & 0 deletions services/core/cmd/oac/oac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
)
Expand Down Expand Up @@ -53,6 +54,7 @@ func TestRotateCoreKeyDigestDoesNotEchoTheKey(t *testing.T) {
t.Fatal(err)
}
key := strings.TrimSpace(string(raw))
assertCoreKeyFormat(t, key)
digest, err := os.ReadFile(filepath.Join(in.data, "secrets", "core", "core-key-digests.json"))
if err != nil || !strings.Contains(string(digest), keyDigest(key)) || strings.Contains(string(digest), key) {
t.Fatalf("digest %s key leaked %v", digest, err)
Expand All @@ -70,3 +72,10 @@ func (s scriptedRunner) Run(_ context.Context, args ...string) error { return s.
func (s scriptedRunner) Output(context.Context, ...string) ([]byte, error) {
return nil, nil
}

func assertCoreKeyFormat(t *testing.T, key string) {
t.Helper()
if !regexp.MustCompile(`^oac_admin_[0-9a-f]{64}$`).MatchString(key) {
t.Fatal("core key must have the oac_admin_ prefix and 64 lowercase hexadecimal characters")
}
}
2 changes: 1 addition & 1 deletion services/web/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const sessionCookie = "core_console_session"
const sessionLifetime = 12 * time.Hour

// minimumCoreKeyLength keeps guessing infeasible even though a correct key is
// never rate limited. Installer-generated keys have 64 characters.
// never rate limited. Installer-generated keys carry 256 bits of random entropy.
const minimumCoreKeyLength = 32

// consoleAuth signs the browser in with the Core key. Sessions live only in
Expand Down
Loading