Skip to content
Closed
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
34 changes: 27 additions & 7 deletions cmd/anubis/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/crypto/hkdf"
)

var (
Expand All @@ -54,6 +55,7 @@ var (
target = flag.String("target", "http://localhost:3923", "target to reverse proxy to")
healthcheck = flag.Bool("healthcheck", false, "run a health check against Anubis")
debugXRealIPDefault = flag.String("debug-x-real-ip-default", "", "If set, replace empty X-Real-Ip headers with this value, useful only for debugging Anubis and running it locally")
secret = flag.String("secret", "", "generate random signing key if empty or use value to derive the key")

//go:embed static botPolicies.json
static embed.FS
Expand Down Expand Up @@ -158,7 +160,12 @@ func main() {
return
}

s, err := New(*target, *policyFname)
private, err := newPrivateKey(*secret)
if err != nil {
log.Fatal(err)
}

s, err := New(*target, *policyFname, private)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -288,15 +295,15 @@ func (s *Server) challengeFor(r *http.Request, difficulty int) string {
return sha256sum(data)
}

func New(target, policyFname string) (*Server, error) {
func New(target, policyFname string, private ed25519.PrivateKey) (*Server, error) {
u, err := url.Parse(target)
if err != nil {
return nil, fmt.Errorf("failed to parse target URL: %w", err)
}

pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("failed to generate ed25519 key: %w", err)
public, ok := private.Public().(ed25519.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key type: %T", private.Public())
}

transport := http.DefaultTransport.(*http.Transport).Clone()
Expand Down Expand Up @@ -342,8 +349,8 @@ func New(target, policyFname string) (*Server, error) {

return &Server{
rp: rp,
priv: priv,
pub: pub,
priv: private,
pub: public,
policy: policy,
dnsblCache: NewDecayMap[string, dnsbl.DroneBLResponse](),
}, nil
Expand Down Expand Up @@ -709,3 +716,16 @@ func serveMainJSWithBestEncoding(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/javascript")
http.ServeFileFS(w, r, static, "static/js/main.mjs")
}

func newPrivateKey(secret string) (ed25519.PrivateKey, error) {
rr := rand.Reader
if secret != "" {
rr = hkdf.New(sha256.New, []byte(secret), nil, []byte("ed25519 signing key"))
}
Comment on lines +722 to +724
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that secret should be a good random value (e.g. head -c 32 /dev/urandom | base64), HKDF is used for "key splitting", here to derive a signing key and maybe later to derive other keys from this (master) secret.

Alternative is to add "passphrase" flag and use password-based KDF (e.g. https://en.wikipedia.org/wiki/PBKDF2) to derive keys from it.


_, private, err := ed25519.GenerateKey(rr)
if err != nil {
return nil, fmt.Errorf("failed to generate ed25519 private key: %w", err)
}
return private, nil
}
1 change: 1 addition & 0 deletions docs/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fixed and clarified installation instructions
- Introduced integration tests using Playwright
- Derive signing key from configurable secret [#70](https://github.com/TecharoHQ/anubis/issues/70)

## v1.14.2

Expand Down
1 change: 1 addition & 0 deletions docs/docs/admin/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Anubis uses these environment variables for configuration:
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.md). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
| `SECRET` | "" (empty) | Generate random signing key if empty or use value to derive the key. Use `head -c 32 /dev/urandom | base64` to create a suitable value. |

## Docker compose

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/prometheus/client_golang v1.21.1
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
github.com/yl2chen/cidranger v1.0.2
golang.org/x/crypto v0.36.0
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
Expand Down
Loading