feat: import Cloudflare tunnel credentials #11
|
|
@ -1,6 +1,7 @@
|
|||
package cloudflare
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
|
|
@ -53,9 +54,42 @@ func IsLegacyRunTokenState(values map[string]string) bool {
|
|||
func ReadCredentialsFile(path string) (Credentials, error) {
|
||||
contents, err := os.ReadFile(path)
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package cloudflare
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
|
@ -31,18 +32,31 @@ func TestStoredTunnelRejectsLegacyOrPartialState(t *testing.T) {
|
|||
|
||||
func TestReadCredentialsFileRequiresKnownFields(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
credentials, err := ReadCredentialsFile(path)
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := ReadCredentialsFile(path); err == nil {
|
||||
t.Fatal("credential file with unknown fields was accepted")
|
||||
if _, err := ReadCredentialsFile(path); err == nil || err.Error() != "invalid Cloudflare credentials file" {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue