// 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) } }