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

fix: prevent server-side request forgery using Kubernetes storage #2479

Merged
merged 1 commit into from
Jan 8, 2023
Merged
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
48 changes: 39 additions & 9 deletions storage/kubernetes/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"net/url"
"os"
"path"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -83,14 +84,26 @@ func offlineTokenName(userID string, connID string, h func() hash.Hash) string {
return strings.TrimRight(encoding.EncodeToString(hash.Sum(nil)), "=")
}

const kubeResourceMaxLen = 63

var kubeResourceNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)

func (cli *client) urlForWithParams(
apiVersion, namespace, resource, name string, params url.Values,
) string {
) (string, error) {
basePath := "apis/"
if apiVersion == "v1" {
basePath = "api/"
}

if name != "" && (len(name) > kubeResourceMaxLen || !kubeResourceNameRegex.MatchString(name)) {
// The actual name can be found in auth request or auth code objects and equals to the state value
return "", fmt.Errorf(
nabokihms marked this conversation as resolved.
Show resolved Hide resolved
"invalid kubernetes resource name: must match the pattern %s and be no longer than %d charactes",
Copy link
Member

Choose a reason for hiding this comment

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

I don't really want to nitpick, but Kubernetes has a pretty good error message for invalid names. Can't we just use that?

Maybe even use the same library to validate the resource name?

Anyway, we can always change the error later.

Copy link
Member Author

Choose a reason for hiding this comment

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

It seems like related to
#1848

Copy link
Member

Choose a reason for hiding this comment

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

Let's look into that if we can

kubeResourceNameRegex.String(),
kubeResourceMaxLen)
}

var p string
if namespace != "" {
p = path.Join(basePath, apiVersion, "namespaces", namespace, resource, name)
Expand All @@ -105,13 +118,13 @@ func (cli *client) urlForWithParams(
}

if strings.HasSuffix(cli.baseURL, "/") {
return cli.baseURL + p + paramsSuffix
return cli.baseURL + p + paramsSuffix, nil
}

return cli.baseURL + "/" + p + paramsSuffix
return cli.baseURL + "/" + p + paramsSuffix, nil
}

func (cli *client) urlFor(apiVersion, namespace, resource, name string) string {
func (cli *client) urlFor(apiVersion, namespace, resource, name string) (string, error) {
return cli.urlForWithParams(apiVersion, namespace, resource, name, url.Values{})
}

Expand Down Expand Up @@ -191,13 +204,20 @@ func (cli *client) getURL(url string, v interface{}) error {
}

func (cli *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error {
return cli.getURL(cli.urlFor(apiVersion, namespace, resource, name), v)
u, err := cli.urlFor(apiVersion, namespace, resource, name)
if err != nil {
return err
}
return cli.getURL(u, v)
}

func (cli *client) listN(resource string, v interface{}, n int) error {
params := url.Values{}
params.Add("limit", fmt.Sprintf("%d", n))
u := cli.urlForWithParams(cli.apiVersion, cli.namespace, resource, "", params)
u, err := cli.urlForWithParams(cli.apiVersion, cli.namespace, resource, "", params)
if err != nil {
return err
}
return cli.getURL(u, v)
}

Expand All @@ -215,7 +235,10 @@ func (cli *client) postResource(apiVersion, namespace, resource string, v interf
return fmt.Errorf("marshal object: %v", err)
}

url := cli.urlFor(apiVersion, namespace, resource, "")
url, err := cli.urlFor(apiVersion, namespace, resource, "")
if err != nil {
return err
}
resp, err := cli.client.Post(url, "application/json", bytes.NewReader(body))
if err != nil {
return err
Expand Down Expand Up @@ -256,7 +279,10 @@ func (cli *client) detectKubernetesVersion() error {
}

func (cli *client) delete(resource, name string) error {
url := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
url, err := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
if err != nil {
return err
}
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return fmt.Errorf("create delete request: %v", err)
Expand Down Expand Up @@ -295,7 +321,11 @@ func (cli *client) put(resource, name string, v interface{}) error {
return fmt.Errorf("marshal object: %v", err)
}

url := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
url, err := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
if err != nil {
return err
}

req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("create patch request: %v", err)
Expand Down
6 changes: 5 additions & 1 deletion storage/kubernetes/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ func TestURLFor(t *testing.T) {

for _, test := range tests {
c := &client{baseURL: test.baseURL}
got := c.urlFor(test.apiVersion, test.namespace, test.resource, test.name)
got, err := c.urlFor(test.apiVersion, test.namespace, test.resource, test.name)
if err != nil {
t.Errorf("got error: %v", err)
}

if got != test.want {
t.Errorf("(&client{baseURL:%q}).urlFor(%q, %q, %q, %q): expected %q got %q",
test.baseURL,
Expand Down