Skip to content

Commit

Permalink
Change errors to be inside responses instead of using gRPC codes (#8)
Browse files Browse the repository at this point in the history
* Change errors to be inside responses instead of using gRPC codes

This commit updates the IAM runtime spec and protobuf definitions to
put results inside ValidateCredentialResponse and CheckAccessResponse
objects. This allows separation of authentication/authorization of the
caller from that of the subject.

This is a breaking change.

Signed-off-by: John Schaeffer <jschaeffer@equinix.com>

* Update example code to fully conform to new spec

This commit updates the hello world example to use the new runtime
authentication/authorization error semantics.

Signed-off-by: John Schaeffer <jschaeffer@equinix.com>

---------

Signed-off-by: John Schaeffer <jschaeffer@equinix.com>
  • Loading branch information
jnschaeffer authored Feb 28, 2024
1 parent 7a8d111 commit 13fb66c
Show file tree
Hide file tree
Showing 9 changed files with 465 additions and 179 deletions.
29 changes: 20 additions & 9 deletions examples/hello-world/cmd/runtime/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,36 @@ func (s *authorizationServer) CheckAccess(ctx context.Context, req *authorizatio

log.Printf("received token: %s", tok)
if tok != "hello" {
err := status.Error(codes.Unauthenticated, "who are you?")
err := status.Error(codes.InvalidArgument, "who are you?")
return nil, err
}

result := authorization.CheckAccessResponse_RESULT_ALLOWED

for _, action := range req.Actions {
if action.GetAction() != "greet" || action.GetResourceId() != "world" {
err := status.Error(codes.PermissionDenied, "what are you trying to do?")
return nil, err
result = authorization.CheckAccessResponse_RESULT_DENIED
}
}

return &authorization.CheckAccessResponse{}, nil
out := &authorization.CheckAccessResponse{
Result: result,
}

return out, nil
}

type authenticationServer struct {
authentication.UnimplementedAuthenticationServer
}

func (s *authenticationServer) ValidateCredential(ctx context.Context, req *authentication.ValidateCredentialRequest) (*authentication.ValidateCredentialResponse, error) {
if req.GetCredential() != "hello" {
err := status.Error(codes.Unauthenticated, "who are you?")
return nil, err
if req.Credential != "hello" {
out := &authentication.ValidateCredentialResponse{
Result: authentication.ValidateCredentialResponse_RESULT_INVALID,
}

return out, nil
}

claimsMap := map[string]any{
Expand All @@ -63,8 +71,11 @@ func (s *authenticationServer) ValidateCredential(ctx context.Context, req *auth
}

out := &authentication.ValidateCredentialResponse{
SubjectId: "hello",
Claims: claims,
Result: authentication.ValidateCredentialResponse_RESULT_VALID,
Subject: &authentication.Subject{
SubjectId: "hello",
Claims: claims,
},
}

return out, nil
Expand Down
71 changes: 45 additions & 26 deletions examples/hello-world/cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import (
"github.com/metal-toolbox/iam-runtime/pkg/iam/runtime/authentication"
"github.com/metal-toolbox/iam-runtime/pkg/iam/runtime/authorization"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
)

var (
Expand All @@ -31,49 +29,61 @@ type server struct {
}
}

func errorToHTTPStatus(err error) (int, string) {
switch status.Code(err) {
case codes.PermissionDenied:
return http.StatusForbidden, "can't do that!"
case codes.Unauthenticated:
return http.StatusUnauthorized, "who are you?"
default:
return http.StatusInternalServerError, "something broke, sorry"
}
}

func writeError(w http.ResponseWriter, err error) {
status, msg := errorToHTTPStatus(err)

func writeMessage(w http.ResponseWriter, status int, msg string) {
w.WriteHeader(status)
w.Write([]byte(msg + "\n"))
if _, err := w.Write([]byte(msg + "\n")); err != nil {
log.Printf("error writing response: %v", err)
}
}

func (s *server) handleWhoAmI(w http.ResponseWriter, req *http.Request) {
authRequest := &authentication.ValidateCredentialRequest{
validateRequest := &authentication.ValidateCredentialRequest{
Credential: getToken(req),
}

resp, err := s.runtime.ValidateCredential(req.Context(), authRequest)
resp, err := s.runtime.ValidateCredential(req.Context(), validateRequest)
if err != nil {
log.Printf("error getting user info: %v", err)
writeError(w, err)
writeMessage(w, http.StatusInternalServerError, err.Error())

return
}

sub := resp.SubjectId
msg := fmt.Sprintf("you are: %s\n", sub)
if resp.Result == authentication.ValidateCredentialResponse_RESULT_INVALID {
writeMessage(w, http.StatusUnauthorized, "who are you?")

if _, err := w.Write([]byte(msg)); err != nil {
log.Printf("error writing response: %v", err)
return
}

sub := resp.Subject.SubjectId
msg := fmt.Sprintf("you are: %s\n", sub)

writeMessage(w, http.StatusOK, msg)
}

func (s *server) handleCanI(w http.ResponseWriter, req *http.Request) {
query := req.URL.Query()
what := query.Get("what")
who := query.Get("who")

validateRequest := &authentication.ValidateCredentialRequest{
Credential: getToken(req),
}

credsResp, err := s.runtime.ValidateCredential(req.Context(), validateRequest)
if err != nil {
log.Printf("error getting user info: %v", err)
writeMessage(w, http.StatusInternalServerError, err.Error())

return
}

if credsResp.Result == authentication.ValidateCredentialResponse_RESULT_INVALID {
writeMessage(w, http.StatusUnauthorized, "who are you?")

return
}

accessRequest := &authorization.CheckAccessRequest{
Credential: getToken(req),
Actions: []*authorization.AccessRequestAction{
Expand All @@ -84,9 +94,18 @@ func (s *server) handleCanI(w http.ResponseWriter, req *http.Request) {
},
}

if _, err := s.runtime.CheckAccess(req.Context(), accessRequest); err != nil {
accessResp, err := s.runtime.CheckAccess(req.Context(), accessRequest)

if err != nil {
log.Printf("error trying to do the thing: %v", err)
writeError(w, err)
writeMessage(w, http.StatusInternalServerError, err.Error())

return
}

if accessResp.Result == authorization.CheckAccessResponse_RESULT_DENIED {
writeMessage(w, http.StatusForbidden, "no!")

return
}

Expand Down
Loading

0 comments on commit 13fb66c

Please # to comment.