Skip to content

Commit d52ed96

Browse files
authored
guard against unstructured url forwarding in CNS API's (#1192)
* remove forwarding unstructured urls * remove forwarding unstructured urls * addressing feedback * fix linting issues * update getncversion test * fix test issue where nmagent handler returns 500 * update for linter * address comments * small liniter changes * quick linting changes * lint * use gofumpt instead of gofmt * ignore line count
1 parent 667743d commit d52ed96

File tree

6 files changed

+281
-91
lines changed

6 files changed

+281
-91
lines changed

cns/NetworkContainerContract.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,12 @@ type PublishNetworkContainerRequest struct {
463463
CreateNetworkContainerRequestBody []byte
464464
}
465465

466+
// NetworkContainerParameters parameters available in network container operations
467+
type NetworkContainerParameters struct {
468+
AuthToken string
469+
AssociatedInterfaceID string
470+
}
471+
466472
// PublishNetworkContainerResponse specifies the response to publish network container request.
467473
type PublishNetworkContainerResponse struct {
468474
Response Response

cns/nmagent/client.go

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"io"
1010
"net/http"
11+
"net/url"
1112
"time"
1213

1314
"github.com/Azure/azure-container-networking/cns/logger"
@@ -20,11 +21,16 @@ const (
2021
GetNmAgentSupportedApiURLFmt = "http://%s/machine/plugins/?comp=nmagent&type=GetSupportedApis"
2122
GetNetworkContainerVersionURLFmt = "http://%s/machine/plugins/?comp=nmagent&type=NetworkManagement/interfaces/%s/networkContainers/%s/version/authenticationToken/%s/api-version/1"
2223
GetNcVersionListWithOutTokenURLFmt = "http://%s/machine/plugins/?comp=nmagent&type=NetworkManagement/interfaces/api-version/%s"
24+
JoinNetworkURLFmt = "NetworkManagement/joinedVirtualNetworks/%s/api-version/1"
25+
PutNetworkValueFmt = "NetworkManagement/interfaces/%s/networkContainers/%s/authenticationToken/%s/api-version/1"
26+
DeleteNetworkContainerURLFmt = "NetworkManagement/interfaces/%s/networkContainers/%s/authenticationToken/%s/api-version/1/method/DELETE"
2327
)
2428

2529
// WireServerIP - wire server ip
2630
var (
2731
WireserverIP = "168.63.129.16"
32+
WireServerPath = "machine/plugins"
33+
WireServerScheme = "http"
2834
getNcVersionListWithOutTokenURLVersion = "2"
2935
)
3036

@@ -65,13 +71,30 @@ func NewClient(url string) (*Client, error) {
6571
}
6672

6773
// JoinNetwork joins the given network
68-
func JoinNetwork(networkID, joinNetworkURL string) (*http.Response, error) {
74+
func JoinNetwork(networkID string) (*http.Response, error) {
6975
logger.Printf("[NMAgentClient] JoinNetwork: %s", networkID)
7076

7177
// Empty body is required as wireserver cannot handle a post without the body.
7278
var body bytes.Buffer
7379
json.NewEncoder(&body).Encode("")
74-
response, err := common.GetHttpClient().Post(joinNetworkURL, "application/json", &body)
80+
81+
joinNetworkTypeValue := fmt.Sprintf(
82+
JoinNetworkURLFmt,
83+
networkID)
84+
85+
joinNetworkURL := url.URL{
86+
Host: WireserverIP,
87+
Path: WireServerPath,
88+
Scheme: WireServerScheme,
89+
}
90+
91+
queryString := joinNetworkURL.Query()
92+
queryString.Set("type", joinNetworkTypeValue)
93+
queryString.Set("comp", "nmagent")
94+
95+
joinNetworkURL.RawQuery = queryString.Encode()
96+
97+
response, err := common.PostCtx(context.TODO(), common.GetHttpClient(), joinNetworkURL.String(), "application/json", &body)
7598

7699
if err == nil && response.StatusCode == http.StatusOK {
77100
defer response.Body.Close()
@@ -84,11 +107,29 @@ func JoinNetwork(networkID, joinNetworkURL string) (*http.Response, error) {
84107
}
85108

86109
// PublishNetworkContainer publishes given network container
87-
func PublishNetworkContainer(networkContainerID, createNetworkContainerURL string, requestBodyData []byte) (*http.Response, error) {
110+
func PublishNetworkContainer(networkContainerID, associatedInterfaceID, accessToken string, requestBodyData []byte) (*http.Response, error) {
88111
logger.Printf("[NMAgentClient] PublishNetworkContainer NC: %s", networkContainerID)
89112

113+
createNcTypeValue := fmt.Sprintf(
114+
PutNetworkValueFmt,
115+
associatedInterfaceID,
116+
networkContainerID,
117+
accessToken)
118+
119+
createURL := url.URL{
120+
Host: WireserverIP,
121+
Path: WireServerPath,
122+
Scheme: WireServerScheme,
123+
}
124+
125+
queryString := createURL.Query()
126+
queryString.Set("type", createNcTypeValue)
127+
queryString.Set("comp", "nmagent")
128+
129+
createURL.RawQuery = queryString.Encode()
130+
90131
requestBody := bytes.NewBuffer(requestBodyData)
91-
response, err := common.GetHttpClient().Post(createNetworkContainerURL, "application/json", requestBody)
132+
response, err := common.PostCtx(context.TODO(), common.GetHttpClient(), createURL.String(), "application/json", requestBody)
92133

93134
logger.Printf("[NMAgentClient][Response] Publish NC: %s. Response: %+v. Error: %v",
94135
networkContainerID, response, err)
@@ -97,13 +138,31 @@ func PublishNetworkContainer(networkContainerID, createNetworkContainerURL strin
97138
}
98139

99140
// UnpublishNetworkContainer unpublishes given network container
100-
func UnpublishNetworkContainer(networkContainerID, deleteNetworkContainerURL string) (*http.Response, error) {
141+
func UnpublishNetworkContainer(networkContainerID, associatedInterfaceID, accessToken string) (*http.Response, error) {
101142
logger.Printf("[NMAgentClient] UnpublishNetworkContainer NC: %s", networkContainerID)
102143

144+
deleteNCTypeValue := fmt.Sprintf(
145+
DeleteNetworkContainerURLFmt,
146+
associatedInterfaceID,
147+
networkContainerID,
148+
accessToken)
149+
150+
deleteURL := url.URL{
151+
Host: WireserverIP,
152+
Path: WireServerPath,
153+
Scheme: WireServerScheme,
154+
}
155+
156+
queryString := deleteURL.Query()
157+
queryString.Set("type", deleteNCTypeValue)
158+
queryString.Set("comp", "nmagent")
159+
160+
deleteURL.RawQuery = queryString.Encode()
161+
103162
// Empty body is required as wireserver cannot handle a post without the body.
104163
var body bytes.Buffer
105164
json.NewEncoder(&body).Encode("")
106-
response, err := common.GetHttpClient().Post(deleteNetworkContainerURL, "application/json", &body)
165+
response, err := common.PostCtx(context.TODO(), common.GetHttpClient(), deleteURL.String(), "application/json", &body)
107166

108167
logger.Printf("[NMAgentClient][Response] Unpublish NC: %s. Response: %+v. Error: %v",
109168
networkContainerID, response, err)

cns/restserver/api.go

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ package restserver
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"io"
1011
"net"
1112
"net/http"
1213
"net/url"
14+
"regexp"
1315
"runtime"
1416
"strings"
1517

@@ -23,6 +25,19 @@ import (
2325
"github.com/Azure/azure-container-networking/platform"
2426
)
2527

28+
var (
29+
ncRegex = regexp.MustCompile(`NetworkManagement/interfaces/(.{0,36})/networkContainers/(.{0,36})/authenticationToken/(.{0,36})/api-version/1(/method/DELETE)?`)
30+
ErrInvalidNcURLFormat = errors.New("Invalid network container url format")
31+
)
32+
33+
// ncURLExpectedMatches defines the size of matches expected from exercising the ncRegex
34+
// 1) the original url (nuance related to golangs regex package)
35+
// 2) the associated interface parameter
36+
// 3) the ncid parameter
37+
// 4) the authentication token parameter
38+
// 5) the optional delete path
39+
const ncURLExpectedMatches = 5
40+
2641
// This file contains implementation of all HTTP APIs which are exposed to external clients.
2742
// TODO: break it even further per module (network, nc, etc) like it is done for ipam
2843

@@ -1072,22 +1087,30 @@ func (service *HTTPRestService) getNumberOfCPUCores(w http.ResponseWriter, r *ht
10721087
logger.Response(service.Name, numOfCPUCoresResp, resp.ReturnCode, err)
10731088
}
10741089

1075-
func getInterfaceIdFromCreateNetworkContainerURL(
1076-
createNetworkContainerURL string) string {
1077-
return strings.Split(strings.Split(createNetworkContainerURL, "interfaces/")[1], "/")[0]
1078-
}
1090+
func getAuthTokenAndInterfaceIDFromNcURL(networkContainerURL string) (*cns.NetworkContainerParameters, error) {
1091+
ncURL, err := url.Parse(networkContainerURL)
1092+
if err != nil {
1093+
return nil, fmt.Errorf("failed to parse network container url, %w", err)
1094+
}
10791095

1080-
func getAuthTokenFromCreateNetworkContainerURL(
1081-
createNetworkContainerURL string) string {
1082-
return strings.Split(strings.Split(createNetworkContainerURL, "authenticationToken/")[1], "/")[0]
1083-
}
1096+
queryParams := ncURL.Query()
10841097

1085-
func extractHostFromJoinNetworkURL(urlToParse string) string {
1086-
parsedURL, err := url.Parse(urlToParse)
1087-
if err != nil {
1088-
return ""
1098+
// current format of create network url has a path after a query parameter "type"
1099+
// doing this parsing due to this structure
1100+
typeQueryParamVal := queryParams.Get("type")
1101+
if typeQueryParamVal == "" {
1102+
return nil, fmt.Errorf("no type query param, %w", ErrInvalidNcURLFormat)
10891103
}
1090-
return parsedURL.Host
1104+
1105+
// .{0,128} gets from zero to 128 characters of any kind
1106+
// ()? is optional
1107+
matches := ncRegex.FindStringSubmatch(typeQueryParamVal)
1108+
1109+
if len(matches) != ncURLExpectedMatches {
1110+
return nil, fmt.Errorf("unexpected number of matches in url, %w", ErrInvalidNcURLFormat)
1111+
}
1112+
1113+
return &cns.NetworkContainerParameters{AssociatedInterfaceID: matches[1], AuthToken: matches[3]}, nil
10911114
}
10921115

10931116
// Publish Network Container by calling nmagent
@@ -1109,6 +1132,8 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r
11091132

11101133
err = service.Listener.Decode(w, r, &req)
11111134

1135+
creteNcURLCopy := req.CreateNetworkContainerURL
1136+
11121137
// reqCopy creates a copy of incoming request. It doesn't copy the authentication token info
11131138
// to avoid logging it.
11141139
reqCopy := cns.PublishNetworkContainerRequest{
@@ -1119,14 +1144,38 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r
11191144
}
11201145

11211146
logger.Request(service.Name, &reqCopy, err)
1147+
1148+
// TODO - refactor this method for better error handling
11221149
if err != nil {
11231150
return
11241151
}
11251152

1153+
var ncParameters *cns.NetworkContainerParameters
1154+
ncParameters, err = getAuthTokenAndInterfaceIDFromNcURL(creteNcURLCopy)
1155+
if err != nil {
1156+
logger.Errorf("[Azure-CNS] nc parameters validation failed with %+v", err)
1157+
w.WriteHeader(http.StatusBadRequest)
1158+
1159+
badRequestResponse := &cns.PublishNetworkContainerResponse{
1160+
Response: cns.Response{
1161+
ReturnCode: http.StatusBadRequest,
1162+
Message: fmt.Sprintf("Request contains a unexpected create url format in request body: %v", reqCopy.CreateNetworkContainerURL),
1163+
},
1164+
PublishErrorStr: fmt.Sprintf("Bad request: Request contains a unexpected create url format in request body: %v", reqCopy.CreateNetworkContainerURL),
1165+
PublishStatusCode: http.StatusBadRequest,
1166+
}
1167+
err = service.Listener.Encode(w, &badRequestResponse)
1168+
logger.Response(service.Name, badRequestResponse, badRequestResponse.Response.ReturnCode, err)
1169+
return
1170+
}
1171+
11261172
switch r.Method {
11271173
case "POST":
11281174
// Join the network
1129-
publishResponse, publishError, err = service.joinNetwork(req.NetworkID, req.JoinNetworkURL)
1175+
// Please refactor this
1176+
// do not reuse the below variable between network join and publish
1177+
// nolint:bodyclose // existing code needs refactoring
1178+
publishResponse, publishError, err = service.joinNetwork(req.NetworkID)
11301179
if err == nil {
11311180
isNetworkJoined = true
11321181
} else {
@@ -1136,9 +1185,12 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r
11361185

11371186
if isNetworkJoined {
11381187
// Publish Network Container
1188+
1189+
// nolint:bodyclose // existing code needs refactoring
11391190
publishResponse, publishError = nmagent.PublishNetworkContainer(
11401191
req.NetworkContainerID,
1141-
req.CreateNetworkContainerURL,
1192+
ncParameters.AssociatedInterfaceID,
1193+
ncParameters.AuthToken,
11421194
req.CreateNetworkContainerRequestBody)
11431195
if publishError != nil || publishResponse.StatusCode != http.StatusOK {
11441196
returnMessage = fmt.Sprintf("Failed to publish Network Container: %s", req.NetworkContainerID)
@@ -1149,20 +1201,11 @@ func (service *HTTPRestService) publishNetworkContainer(w http.ResponseWriter, r
11491201
}
11501202

11511203
// Store ncGetVersionURL needed for calling NMAgent to check if vfp programming is completed for the NC
1152-
primaryInterfaceIdentifier := getInterfaceIdFromCreateNetworkContainerURL(req.CreateNetworkContainerURL)
1153-
authToken := getAuthTokenFromCreateNetworkContainerURL(req.CreateNetworkContainerURL)
1154-
1155-
// we attempt to extract the wireserver IP to use from the request, otherwise default to the well-known IP.
1156-
hostIP := extractHostFromJoinNetworkURL(req.JoinNetworkURL)
1157-
if hostIP == "" {
1158-
hostIP = nmagent.WireserverIP
1159-
}
1160-
11611204
ncGetVersionURL := fmt.Sprintf(nmagent.GetNetworkContainerVersionURLFmt,
1162-
hostIP,
1163-
primaryInterfaceIdentifier,
1205+
nmagent.WireserverIP,
1206+
ncParameters.AssociatedInterfaceID,
11641207
req.NetworkContainerID,
1165-
authToken)
1208+
ncParameters.AuthToken)
11661209
ncVersionURLs.Store(cns.SwiftPrefix+req.NetworkContainerID, ncGetVersionURL)
11671210

11681211
default:
@@ -1219,6 +1262,8 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter,
12191262

12201263
err = service.Listener.Decode(w, r, &req)
12211264

1265+
deleteNcURLCopy := req.DeleteNetworkContainerURL
1266+
12221267
// reqCopy creates a copy of incoming request. It doesn't copy the authentication token info
12231268
// to avoid logging it.
12241269
reqCopy := cns.UnpublishNetworkContainerRequest{
@@ -1233,12 +1278,32 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter,
12331278
return
12341279
}
12351280

1281+
var ncParameters *cns.NetworkContainerParameters
1282+
ncParameters, err = getAuthTokenAndInterfaceIDFromNcURL(deleteNcURLCopy)
1283+
if err != nil {
1284+
logger.Errorf("[Azure-CNS] nc parameters validation failed with %+v", err)
1285+
w.WriteHeader(http.StatusBadRequest)
1286+
1287+
badRequestResponse := &cns.UnpublishNetworkContainerResponse{
1288+
Response: cns.Response{
1289+
ReturnCode: http.StatusBadRequest,
1290+
Message: fmt.Sprintf("Request contains a unexpected delete url format in request body: %v", reqCopy.DeleteNetworkContainerURL),
1291+
},
1292+
UnpublishErrorStr: fmt.Sprintf("Bad request: Request contains a unexpected delete url format in request body: %v", reqCopy.DeleteNetworkContainerURL),
1293+
UnpublishStatusCode: http.StatusBadRequest,
1294+
}
1295+
err = service.Listener.Encode(w, &badRequestResponse)
1296+
logger.Response(service.Name, badRequestResponse, badRequestResponse.Response.ReturnCode, err)
1297+
return
1298+
}
1299+
12361300
switch r.Method {
12371301
case "POST":
12381302
// Join Network if not joined already
12391303
isNetworkJoined = service.isNetworkJoined(req.NetworkID)
12401304
if !isNetworkJoined {
1241-
unpublishResponse, unpublishError, err = service.joinNetwork(req.NetworkID, req.JoinNetworkURL)
1305+
// nolint:bodyclose // existing code needs refactoring
1306+
unpublishResponse, unpublishError, err = service.joinNetwork(req.NetworkID)
12421307
if err == nil {
12431308
isNetworkJoined = true
12441309
} else {
@@ -1251,7 +1316,8 @@ func (service *HTTPRestService) unpublishNetworkContainer(w http.ResponseWriter,
12511316
// Unpublish Network Container
12521317
unpublishResponse, unpublishError = nmagent.UnpublishNetworkContainer(
12531318
req.NetworkContainerID,
1254-
req.DeleteNetworkContainerURL)
1319+
ncParameters.AssociatedInterfaceID,
1320+
ncParameters.AuthToken)
12551321
if unpublishError != nil || unpublishResponse.StatusCode != http.StatusOK {
12561322
returnMessage = fmt.Sprintf("Failed to unpublish Network Container: %s", req.NetworkContainerID)
12571323
returnCode = types.NetworkContainerUnpublishFailed

0 commit comments

Comments
 (0)