feat: import Cloudflare tunnel credentials #11

Merged
eding merged 2 commits from fix/import-cloudflare-tunnel-credentials into main 2026-08-04 20:55:35 +02:00
2 changed files with 58 additions and 10 deletions
Showing only changes of commit a08eb8616d - Show all commits

View file

@ -1,6 +1,7 @@
package cloudflare package cloudflare
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
@ -53,9 +54,42 @@ func IsLegacyRunTokenState(values map[string]string) bool {
func ReadCredentialsFile(path string) (Credentials, error) { func ReadCredentialsFile(path string) (Credentials, error) {
contents, err := os.ReadFile(path) contents, err := os.ReadFile(path)
if err != nil { if err != nil {
return Credentials{}, errors.New("read Cloudflare credentials file") return Credentials{}, errors.New("invalid Cloudflare credentials file")
} }
return parseCredentials(contents) if credentials, err := parseCredentials(contents); err == nil {
return credentials, nil
}
credentials, err := credentialsFromSecret(contents)
if err != nil {
return Credentials{}, errors.New("invalid Cloudflare credentials file")
}
return credentials, nil
}
func credentialsFromSecret(contents []byte) (Credentials, error) {
var secret struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
StringData map[string]string `yaml:"stringData"`
Data map[string]string `yaml:"data"`
}
decoder := yaml.NewDecoder(strings.NewReader(string(contents)))
if err := decoder.Decode(&secret); err != nil || decoder.Decode(&struct{}{}) != io.EOF || secret.APIVersion != "v1" || secret.Kind != "Secret" {
return Credentials{}, errors.New("invalid Secret")
}
plaintext, inStringData := secret.StringData[credentialsKey]
encoded, inData := secret.Data[credentialsKey]
if inStringData == inData {
return Credentials{}, errors.New("missing Secret credentials")
}
if inStringData {
return parseCredentials([]byte(plaintext))
}
decoded, err := base64.StdEncoding.DecodeString(encoded)
if err != nil {
return Credentials{}, errors.New("invalid Secret credentials")
}
return parseCredentials(decoded)
} }
func NewRoute(hostname, service string) (Route, error) { func NewRoute(hostname, service string) (Route, error) {

View file

@ -1,6 +1,7 @@
package cloudflare package cloudflare
import ( import (
"encoding/base64"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
@ -31,18 +32,31 @@ func TestStoredTunnelRejectsLegacyOrPartialState(t *testing.T) {
func TestReadCredentialsFileRequiresKnownFields(t *testing.T) { func TestReadCredentialsFileRequiresKnownFields(t *testing.T) {
path := filepath.Join(t.TempDir(), "credentials.json") path := filepath.Join(t.TempDir(), "credentials.json")
if err := os.WriteFile(path, []byte(`{"AccountTag":"account","TunnelSecret":"secret","TunnelID":"tunnel"}`), 0600); err != nil { raw := `{"AccountTag":"account","TunnelSecret":"secret","TunnelID":"tunnel"}`
for _, contents := range []string{
raw,
"apiVersion: v1\nkind: Secret\nstringData:\n credentials.json: |\n " + raw + "\n",
"apiVersion: v1\nkind: Secret\ndata:\n credentials.json: " + base64.StdEncoding.EncodeToString([]byte(raw)) + "\n",
} {
if err := os.WriteFile(path, []byte(contents), 0600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
credentials, err := ReadCredentialsFile(path) credentials, err := ReadCredentialsFile(path)
if err != nil || credentials.TunnelID != "tunnel" { if err != nil || credentials.TunnelID != "tunnel" {
t.Fatal("valid credential file was not accepted") t.Fatal("supported credential file was not accepted")
}
} }
if err := os.WriteFile(path, []byte(`{"AccountTag":"account","TunnelSecret":"secret","TunnelID":"tunnel","extra":"value"}`), 0600); err != nil { if err := os.WriteFile(path, []byte(`{"AccountTag":"account","TunnelSecret":"secret","TunnelID":"tunnel","extra":"value"}`), 0600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := ReadCredentialsFile(path); err == nil { if _, err := ReadCredentialsFile(path); err == nil || err.Error() != "invalid Cloudflare credentials file" {
t.Fatal("credential file with unknown fields was accepted") t.Fatal("credential file with unknown fields did not return a safe error")
}
if err := os.WriteFile(path, []byte("apiVersion: v1\nkind: Secret\ndata: {}\n"), 0600); err != nil {
t.Fatal(err)
}
if _, err := ReadCredentialsFile(path); err == nil || err.Error() != "invalid Cloudflare credentials file" {
t.Fatal("Secret without credentials did not return a safe error")
} }
} }