200 lines
6.0 KiB
Go
200 lines
6.0 KiB
Go
// The goapp command implements a simple App Engine app to demonstrate how to
|
|
// use the Service Control API v2 for admission control. For more information,
|
|
// see https://cloud.google.com/service-infrastructure/docs/admission-control.
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
// WARNING:`go get google.golang.org/api/servicecontrol/v2" may take
|
|
// 30 minutes or longer, depending on your network speed.
|
|
"google.golang.org/api/servicecontrol/v2"
|
|
)
|
|
|
|
// Check calls Service Control API v2 for admission control.
|
|
// Name specifies the target resource name. Permission specifies
|
|
// the required permission on the target resource. Received
|
|
// specifies the timestamp when the request is received.
|
|
func check(w http.ResponseWriter, r *http.Request, name string, permission string, received time.Time, client *servicecontrol.Service) (string, error) {
|
|
// Construct CheckRequest from the incoming HTTP request.
|
|
// The code assumes the incoming request processed by App Engine ingress.
|
|
req := &servicecontrol.CheckRequest{
|
|
ServiceConfigId: "latest",
|
|
Attributes: &servicecontrol.AttributeContext{
|
|
Origin: &servicecontrol.Peer{
|
|
Ip: r.Header.Get("x-appengine-user-ip"),
|
|
},
|
|
Api: &servicecontrol.Api{
|
|
Service: "endpointsapis.appspot.com",
|
|
Operation: "google.example.endpointsapis.v1.Workspaces.GetWorkspace",
|
|
Version: "v1",
|
|
Protocol: r.Header.Get("x-forwarded-proto"),
|
|
},
|
|
Request: &servicecontrol.Request{
|
|
Id: r.Header.Get("x-appengine-request-log-id"),
|
|
Time: received.UTC().Format(time.RFC3339),
|
|
Method: r.Method,
|
|
Scheme: r.Header.Get("x-forwarded-proto"),
|
|
Host: r.Host,
|
|
Path: r.URL.Path,
|
|
Headers: map[string]string{
|
|
"authorization": r.Header.Get("authorization"),
|
|
"user-agent": r.Header.Get("user-agent"),
|
|
"origin": r.Header.Get("origin"),
|
|
"referer": r.Header.Get("referer"),
|
|
},
|
|
},
|
|
Resource: &servicecontrol.Resource{
|
|
Name: name,
|
|
},
|
|
},
|
|
Resources: []*servicecontrol.ResourceInfo{
|
|
{
|
|
Name: name,
|
|
Type: "endpointsapis.appspot.com/Workspace",
|
|
Permission: permission,
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Services.Check("endpointsapis.appspot.com", req).Do()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
json, err := resp.MarshalJSON()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(json), nil
|
|
}
|
|
|
|
// Report calls Service Control API v2 for telemetry reporting.
|
|
// Name specifies the target resource name. ResponseCode specifies
|
|
// the response code returned to user. Received specifies the
|
|
// timestamp when the request is received.
|
|
func report(w http.ResponseWriter, r *http.Request, name string, responseCode int64, received time.Time, client *servicecontrol.Service) (string, error) {
|
|
// Construct ReportRequest from the incoming HTTP request.
|
|
// The code assumes the incoming request processed by App Engine ingress.
|
|
req := &servicecontrol.ReportRequest{
|
|
ServiceConfigId: "latest",
|
|
Operations: []*servicecontrol.AttributeContext{
|
|
{
|
|
Api: &servicecontrol.Api{
|
|
Service: "endpointsapis.appspot.com",
|
|
Operation: "google.example.endpointsapis.v1.Workspaces.GetWorkspace",
|
|
Version: "v1",
|
|
Protocol: r.Header.Get("x-forwarded-proto"),
|
|
},
|
|
Request: &servicecontrol.Request{
|
|
Size: r.ContentLength,
|
|
Time: received.UTC().Format(time.RFC3339),
|
|
},
|
|
Response: &servicecontrol.Response{
|
|
Time: time.Now().UTC().Format(time.RFC3339),
|
|
Code: responseCode,
|
|
Headers: map[string]string{
|
|
"x-backend-latency": "0.007",
|
|
},
|
|
},
|
|
Destination: &servicecontrol.Peer{
|
|
RegionCode: "us-central1",
|
|
},
|
|
Resource: &servicecontrol.Resource{
|
|
Name: name,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
_, err := client.Services.Report("endpointsapis.appspot.com", req).Do()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "{}", nil
|
|
}
|
|
|
|
// Parse processes the request path and extract the resource name and
|
|
// permissions.
|
|
func parse(r *http.Request) (string, string, error) {
|
|
// Split the request path.
|
|
segments := strings.Split(r.URL.Path, "/")
|
|
|
|
// The request path must match "/v1/projects/*/locations/*/workspaces/*" or
|
|
// "/v1/projects/*/locations/*/workspaces". They correspond to the
|
|
// GetWorkspace() and ListWorkspaces() methods defined in ../v1/workspace.proto.
|
|
if segments[0] != "" || segments[1] != "v1" || segments[2] != "projects" || segments[4] != "locations" || segments[6] != "workspaces" || len(segments) > 8 {
|
|
return "", "", errors.New("Resource '" + r.URL.Path + "' not found.")
|
|
}
|
|
|
|
// Skip prefix "/v1/".
|
|
resource := r.URL.Path[4:]
|
|
permission := "endpointsapis.appspot.com/workspaces.list"
|
|
if len(segments) == 8 {
|
|
permission = "endpointsapis.appspot.com/workspaces.get"
|
|
}
|
|
return resource, permission, nil
|
|
}
|
|
|
|
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
|
received := time.Now()
|
|
|
|
// Create a client for Service Control API v2.
|
|
client, err := servicecontrol.NewService(r.Context())
|
|
if err != nil {
|
|
fmt.Fprintln(w, "Error:")
|
|
fmt.Fprintln(w, err.Error())
|
|
return
|
|
}
|
|
|
|
resource, permission, err := parse(r)
|
|
if err != nil {
|
|
fmt.Fprintln(w, "Error:")
|
|
fmt.Fprintln(w, err.Error())
|
|
return
|
|
}
|
|
|
|
// Perform admission control.
|
|
result, err := check(w, r, resource, permission, received, client)
|
|
|
|
var responseCode int64 = 200
|
|
// Print the admission control result.
|
|
if err != nil {
|
|
fmt.Fprintln(w, "Error:")
|
|
fmt.Fprintln(w, err.Error())
|
|
responseCode = 403
|
|
} else {
|
|
fmt.Fprintln(w, "CheckResponse:")
|
|
fmt.Fprintln(w, result)
|
|
}
|
|
|
|
// Print all environment variables.
|
|
fmt.Fprintln(w, "Environments:")
|
|
fmt.Fprintln(w, strings.Join(os.Environ(), "\n"))
|
|
|
|
// Print all request headers.
|
|
fmt.Fprintln(w, "Headers:")
|
|
for key, values := range r.Header {
|
|
for _, value := range values {
|
|
fmt.Fprintf(w, "%v: %v\n", key, value)
|
|
}
|
|
}
|
|
|
|
// Perform telemetry report.
|
|
report(w, r, resource, responseCode, received, client)
|
|
}
|
|
|
|
func main() {
|
|
http.HandleFunc("/", indexHandler)
|
|
|
|
port := os.Getenv("PORT")
|
|
|
|
log.Printf("Listen and serve on port %s", port)
|
|
if err := http.ListenAndServe(":"+port, nil); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|