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
5 changes: 5 additions & 0 deletions .changes/next-release/enhancement-core-zf6kl82i.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "core",
"description": "Warn when --endpoint-url points outside the trusted vngcloud.vn domain, since grn sends a replayable IAM bearer token with every request (SEC-08). The warning does not block the request"
}
1 change: 1 addition & 0 deletions go/internal/cli/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func NewClient(cmd *cobra.Command, serviceName string) (*client.GreenodeClient,

var baseURL string
if endpointURL != "" {
WarnIfUntrustedEndpoint(endpointURL)
baseURL = endpointURL
} else {
baseURL, err = cfg.GetEndpoint(serviceName)
Expand Down
46 changes: 46 additions & 0 deletions go/internal/cli/endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cli

import (
"fmt"
"net/url"
"os"
"strings"
)

// trustedEndpointSuffix is the domain grn's own services live under. Requests to
// hosts outside it are flagged because grn sends a reusable IAM bearer token
// with every request (see WarnIfUntrustedEndpoint).
const trustedEndpointSuffix = ".vngcloud.vn"

// IsTrustedEndpoint reports whether endpointURL targets a host within the
// trusted vngcloud.vn domain. An empty value means no --endpoint-url override
// was given (the built-in region endpoint is used), which is trusted.
func IsTrustedEndpoint(endpointURL string) bool {
if endpointURL == "" {
return true
}
u, err := url.Parse(endpointURL)
if err != nil {
return false
}
host := u.Hostname()
if host == "" {
return false
}
return host == "vngcloud.vn" || strings.HasSuffix(host, trustedEndpointSuffix)
}

// WarnIfUntrustedEndpoint prints a security warning to stderr when endpointURL
// points outside the trusted vngcloud.vn domain. grn authenticates against the
// real IAM and sends the resulting reusable bearer token to whatever host
// --endpoint-url names, so a malicious or mistyped host can capture and replay
// that token. This warns; it does not block.
func WarnIfUntrustedEndpoint(endpointURL string) {
if IsTrustedEndpoint(endpointURL) {
return
}
u, _ := url.Parse(endpointURL)
fmt.Fprintf(os.Stderr,
"Warning: --endpoint-url %q is outside the trusted %s domain. grn will send your IAM bearer token to this host, and a bearer token can be replayed. Only use endpoints you trust.\n",
u.Hostname(), strings.TrimPrefix(trustedEndpointSuffix, "."))
}
21 changes: 21 additions & 0 deletions go/internal/cli/endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cli

import "testing"

func TestIsTrustedEndpoint(t *testing.T) {
cases := map[string]bool{
"": true, // no override -> built-in endpoint
"https://vks.api.vngcloud.vn": true,
"https://hcm-3.api.vngcloud.vn/x": true,
"https://vngcloud.vn": true,
"http://attacker.com": false,
"https://evil.vngcloud.vn.attacker.com": false, // suffix must be a real domain boundary
"http://localhost:8080": false,
"not-a-url ::::": false,
}
for in, want := range cases {
if got := IsTrustedEndpoint(in); got != want {
t.Errorf("IsTrustedEndpoint(%q) = %v, want %v", in, got, want)
}
}
}
2 changes: 2 additions & 0 deletions go/internal/vserverclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/spf13/cobra"
"github.com/vngcloud/greennode-cli/internal/auth"
"github.com/vngcloud/greennode-cli/internal/cli"
"github.com/vngcloud/greennode-cli/internal/client"
"github.com/vngcloud/greennode-cli/internal/config"
"github.com/vngcloud/greennode-cli/internal/formatter"
Expand Down Expand Up @@ -37,6 +38,7 @@ func BuildClient(cmd *cobra.Command) (*client.GreenodeClient, *config.Config, er

var baseURL string
if endpointURL != "" {
cli.WarnIfUntrustedEndpoint(endpointURL)
baseURL = endpointURL
} else {
baseURL, err = cfg.GetEndpoint("vserver")
Expand Down
Loading