Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add auth.tokenFile support for file-based token authentication #4388

Closed
wants to merge 1 commit into from
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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,33 @@ When specifying `auth.method = "token"` in `frpc.toml` and `frps.toml` - token b

Make sure to specify the same `auth.token` in `frps.toml` and `frpc.toml` for frpc to pass frps validation

#### File-based Token Authentication

If you prefer not to include the token directly in the configuration file, you can specify the path to a file containing the token using the `auth.tokenFile` option.

Instead of setting `auth.token`, specify the path to the file containing your token with `auth.tokenFile`.
Ensure that the file contains only the token and no additional content. Example configuration:

```toml
# frpc.toml
[common]
auth.tokenFile = "/path/to/your/token_file"
```

```toml
# frps.toml
[common]
auth.tokenFile = "/path/to/your/token_file"
```

Make sure the file at the specified path is readable by the application and contains the following:

``` txt
your_secret_token
```

**Notes:** If both `auth.token` and `auth.tokenFile` are specified, the token from `auth.token` will be used.

#### OIDC Authentication

When specifying `auth.method = "oidc"` in `frpc.toml` and `frps.toml` - OIDC based authentication will be used.
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ func RegisterClientCommonConfigFlags(cmd *cobra.Command, c *v1.ClientCommonConfi
}
cmd.PersistentFlags().StringVarP(&c.User, "user", "u", "", "user")
cmd.PersistentFlags().StringVarP(&c.Auth.Token, "token", "t", "", "auth token")
cmd.PersistentFlags().StringVarP(&c.Auth.TokenFile, "token_file", "", "", "auth token file")
}

type PortsRangeSliceFlag struct {
Expand Down Expand Up @@ -240,6 +241,7 @@ func RegisterServerConfigFlags(cmd *cobra.Command, c *v1.ServerConfig, opts ...R
cmd.PersistentFlags().Int64VarP(&c.Log.MaxDays, "log_max_days", "", 3, "log max days")
cmd.PersistentFlags().BoolVarP(&c.Log.DisablePrintColor, "disable_log_color", "", false, "disable log color in console")
cmd.PersistentFlags().StringVarP(&c.Auth.Token, "token", "t", "", "auth token")
cmd.PersistentFlags().StringVarP(&c.Auth.TokenFile, "token_file", "", "", "auth token file")
cmd.PersistentFlags().StringVarP(&c.SubDomainHost, "subdomain_host", "", "", "subdomain host")
cmd.PersistentFlags().VarP(&PortsRangeSliceFlag{V: &c.AllowPorts}, "allow_ports", "", "allow ports")
cmd.PersistentFlags().Int64VarP(&c.MaxPortsPerClient, "max_ports_per_client", "", 0, "max ports per client")
Expand Down
9 changes: 9 additions & 0 deletions pkg/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ func LoadFileContentWithTemplate(path string, values *Values) ([]byte, error) {
return RenderWithTemplate(b, values)
}

func LoadFileToken(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
token := strings.TrimSpace(string(data))
return token, nil
}

func LoadConfigureFromFile(path string, c any, strict bool) error {
content, err := LoadFileContentWithTemplate(path, GetValues())
if err != nil {
Expand Down
12 changes: 10 additions & 2 deletions pkg/config/v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,20 @@ type AuthClientConfig struct {
// Token specifies the authorization token used to create keys to be sent
// to the server. The server must have a matching token for authorization
// to succeed. By default, this value is "".
Token string `json:"token,omitempty"`
OIDC AuthOIDCClientConfig `json:"oidc,omitempty"`
Token string `json:"token,omitempty"`
TokenFile string `json:"tokenFile,omitempty"`
OIDC AuthOIDCClientConfig `json:"oidc,omitempty"`
}

func (c *AuthClientConfig) Complete() {
c.Method = util.EmptyOr(c.Method, "token")

if c.Token == "" && c.TokenFile != "" {
tokenFromFile, err := util.LoadFileToken(c.TokenFile)
if err == nil {
c.Token = tokenFromFile
}
}
}

type AuthOIDCClientConfig struct {
Expand Down
7 changes: 7 additions & 0 deletions pkg/config/v1/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,18 @@ type AuthServerConfig struct {
Method AuthMethod `json:"method,omitempty"`
AdditionalScopes []AuthScope `json:"additionalScopes,omitempty"`
Token string `json:"token,omitempty"`
TokenFile string `json:"tokenFile,omitempty"`
OIDC AuthOIDCServerConfig `json:"oidc,omitempty"`
}

func (c *AuthServerConfig) Complete() {
c.Method = util.EmptyOr(c.Method, "token")
if c.Token == "" && c.TokenFile != "" {
tokenFromFile, err := util.LoadFileToken(c.TokenFile)
if err == nil {
c.Token = tokenFromFile
}
}
}

type AuthOIDCServerConfig struct {
Expand Down
10 changes: 10 additions & 0 deletions pkg/util/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
mathrand "math/rand/v2"
"net"
"os"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -134,3 +135,12 @@ func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Durati
func ConstantTimeEqString(a, b string) bool {
return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1
}

func LoadFileToken(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
token := strings.TrimSpace(string(data))
return token, nil
}
59 changes: 59 additions & 0 deletions test/e2e/v1/basic/client_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package basic

import (
"fmt"
"os"
"strings"
"time"

Expand Down Expand Up @@ -149,6 +150,64 @@ var _ = ginkgo.Describe("[Feature: Client-Server]", func() {
client: `auth.token = "invalid"`,
expectError: true,
})

defineClientServerTest("Client Token File Correct", f, &generalTestConfigures{
server: `auth.token = "123456"`,
client: func() string {
tokenFile := f.WriteTempFile("correct_token.txt", "123456")
framework.AddCleanupAction(func() {
os.Remove(tokenFile)
})
return fmt.Sprintf(`auth.tokenFile = "%s"`, tokenFile)
}(),
})

defineClientServerTest("Server Client Token File Correct", f, &generalTestConfigures{
server: func() string {
tokenFile := f.WriteTempFile("correct_token.txt", "123456")
framework.AddCleanupAction(func() {
os.Remove(tokenFile)
})
return fmt.Sprintf(`auth.tokenFile = "%s"`, tokenFile)
}(),
client: `auth.token = "123456"`,
})

defineClientServerTest("Client Token File Incorrect", f, &generalTestConfigures{
server: `auth.token = "123456"`,
client: func() string {
tokenFile := f.WriteTempFile("incorrect_token.txt", "invalid")
framework.AddCleanupAction(func() {
os.Remove(tokenFile)
})
return fmt.Sprintf(`auth.tokenFile = "%s"`, tokenFile)
}(),
expectError: true,
})

defineClientServerTest("Server Token File Incorrect", f, &generalTestConfigures{
server: func() string {
tokenFile := f.WriteTempFile("incorrect_token.txt", "invalid")
framework.AddCleanupAction(func() {
os.Remove(tokenFile)
})
return fmt.Sprintf(`auth.tokenFile = "%s"`, tokenFile)
}(),
client: `auth.token = "123456"`,
expectError: true,
})

defineClientServerTest("Client Token File Not Exist", f, &generalTestConfigures{
server: `auth.token = "123456"`,
client: `auth.tokenFile = "/path/to/non/existent/file"`,
expectError: true,
})

defineClientServerTest("Server Token File Not Exist", f, &generalTestConfigures{
server: `auth.tokenFile = "/path/to/non/existent/file"`,
client: `auth.token = "123456"`,
expectError: true,
})
})

ginkgo.Describe("TLS", func() {
Expand Down