Why does API Gateway not have permissions for my Authorizer lambda when using Swagger? - swagger

I have an API defined using Swagger, which defines the following API Key authorizer:
securityDefinitions:
api_key:
type: apiKey
name: x-api-key
in: header
x-amazon-apigateway-authtype: "oauth2"
x-amazon-apigateway-authorizer:
type: token
authorizerUri: arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:[accountid]:function:ApiKeyAuthorizerLambdaFunction/invocations
authorizerResultTtlInSeconds: 0
However, when I pass this Swagger definition into my CloudFormation script and call an endpoint secured with this authorizer, I get a 500. The API Gateway logs show the following:
Incoming identity: ***key
Execution failed due to configuration error: Invalid permissions on Lambda function
Execution failed due to configuration error: Authorizer error
Note that I have given API Gateway permission to execute this lambda:
LambdaPermissionAuthorizerApiGateway:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt:
- ApiKeyAuthorizerLambdaFunction
- Arn
Action: lambda:InvokeFunction
Principal: apigateway.amazonaws.com
In fact, when I compare the CloudFormation script which Serverless generates for authoized endpoints (using Serverless's notation) with my own CloudFormation script using Swagger, I see little difference between them, except that my authorizer is defined with Swagger rather than directly as a CF resource.
Can anyone shed any light on this? Is this a bug when using Swagger with CloudFormation?

Can you try setting the authorizerCredentials parameter of your x-amazon-apigateway-authorizer to a valid IAM role that has permission to execute the authorizer lambda? Not sure the standard AWS::Lambda::Permission applies for this, though you probably want to keep it for now just in case it is still required.
The x-amazon-apigateway-authorizer docs show an example.

Related

Pass Along Authorization Header in OpenAPI Generator-generated Java WebClient Code

I have a Java/Spring-based microservices architecture with two services:
A - has a public-facing endpoint which does some stuff and then calls the below endpoint on B. This endpoint requires an Authorization header (OAuth2) to identify the user.
B - has an endpoint that also requires an Authorization header (OAuth2) so that it can determine which user made the call.
I have specified B's endpoint using OpenAPI. I'm using OpenAPI Generator to generate both the client in A (Spring WebClient), and the server in B (Spring Boot).
My question is this: what do I need to do to pass the Authorization header along from A to B? I see how to set a static header, but I don't know how to pass the header based on what's received by A.
Similar to this question, but for WebClient: OpenAPI client generator Java - header per call
As your A service is a resource-server and you want to issue request to service B on behalf of the user who initiated the request to A, just set a Bearer Authorization header on WebClient with the original access-token string retrieved from current security context (use SecurityContextHolder static accessor or have AbstractOAuth2TokenAuthenticationToken<?> auth auto-magically injected by Spring as #Controller method parameter).
If your A service was a client, you could do as I did in the UiController of this tutorial.
Turns out my problem was how I specified the endpoint security in my OpenAPI specification.
I added:
components:
securitySchemes:
s2s:
type: oauth2
flows:
clientCredentials:
authorizationUrl: https://example.com/oauth/authorize
tokenUrl: https://example.com/oauth/token
scopes:
read: Read scope
And made a reference to that security schema on my endpoint:
/foo:
get:
...
security:
- s2s:
- read
Now, when I run openapi-generate on this schema and generate it to either Spring Boot (server) or Java WebClient (client), the generated endpoint signature looks like:
#RequestMapping(
method = RequestMethod.GET,
value = "/foo",
produces = { "application/json" }
)
Mono<ResponseEntity<MyResponse>> foo(
#Parameter(name = "Authorization", description = "", required = true) #RequestHeader(value = "Authorization", required = true) String authorization,
#Parameter(hidden = true) final ServerWebExchange exchange
);
The String authorization argument to the method was not previously being generated and it's what I needed here. It allows me to pass A's header along to the call to B.
Props to #Ch4mp for helping out here.

How to change "scopes" with Auth2.0 (Connexion, Swagger)

I am tying to understand this authentication-example with OAuth2.0 and am stuck at the scopes part: https://github.com/zalando/connexion/tree/master/examples/swagger2/oauth2
In app.yaml, we define 'uid' to be the necessary scope for our application:
/secret:
get:
summary: Return secret string
operationId: app.get_secret
responses:
200:
description: secret response
schema:
type: string
security:
# enable authentication and require the "uid" scope for this endpoint
- oauth2: ['uid']
In the security-definitions of app.yaml, the "uid" is again in the scopes section and is required as an answer from the x-tokenInfoUrl (which I need).
securityDefinitions:
oauth2:
type: oauth2
flow: implicit
authorizationUrl: https://example.com/oauth2/dialog
x-tokenInfoUrl: http://localhost:7979/tokeninfo
scopes:
uid: Unique identifier of the user accessing the service.
In our mock application for tokeninfo (mock_tokeninfo.yaml), we see that "uid" is returned and that the scope is actually "uid", which we wanted.
return {'uid': uid, 'scope': ['uid']}
And finally in mock_tokeninfo.yaml, we have the "uid" and the scope in the response:
responses:
200:
description: Token info object
schema:
type: object
properties:
uid:
type: string
scope:
type: array
items:
type: string
So what I understand now is that when app.py starts on port 8080 and I call localhost on port 8080 with "/secret", the security-part checks what is required and sees "uid". It follows the x-tokenInfoUrl on localhost:7979, where we started our mock_tokeninfo application and it returns to us a "uid" and the scope "uid".
Now my question is the following: I have now an own identity provider and would like to access the userinfo from there. I changed the x-tokenInfoUrl to something like this:
https://<my_idp>/<some_paths>/userinfo
and when I make curl like this: curl -H 'Authorization: Bearer <access_token>‘ https://<IDP>/<some_paths>/userinfo, it works and I get a response that looks like this:
{"mail":"<my_email>",
"name":"<my_name>"}
Now as far as I understand, I would only need to change "uid" to e.g. "mail" and it should return my email to me, but no: I get
403 - forbidden
provided token does not have the required scope
Content-Type: application/problem+json
I don't understand why I don't have the required scope - it simply does not make sense to me. Can somebody please explain, how can I get my information out of my identity provider? I also checked and in my authorization email with my id and secret, it says that scope is <some_keyword>, but this keyword also results in 403 like everything else.
Ps: I've already passed my certificate to the identity provider, so this should not be the problem.
EDIT:
Please - help me you intelligent ppl of StackOverflow :(
I found those "MUST naming conventions for scopes": https://opensource.zalando.com/restful-api-guidelines/
But it did not help as well.
I checked, if the header is actually redirected and it was.
I found this statement: "However, to make this explicit you should assign the uid pseudo permission in this case. It is the user id and always available as OAuth2 default scope", but again - if this is a pseudo-permission, how do I do a normal scope?
In every example that I find (the pet-read/write example is the most common one), the scope-variables seem to have custom names... (https://swagger.io/docs/specification/authentication/oauth2/)
Here the documentation for the "security"-section, maybe I misunderstand something: https://connexion.readthedocs.io/en/latest/security.html
So the solution is finally found. The problem is that the documentation was not updated. This is the link to the documentation:
https://connexion.readthedocs.io/en/latest/security.html#oauth-2-authentication-and-authorization
And it says that:
The sub property of the Token Info response will be passed in the user argument to the handler function.
Further investigation resulted in finding this commit message of the connexion-package:
https://github.com/mnylen/connexion/commit/1818c34ecbdd6833a6c8cde61021ece38b59d978
Which updates the insufficient description by that phrase:
The Token Info response will be passed in the token_info argument to the handler
function. The sub property of the Token Info response will be passed in the user
argument to the handler function.
So putting the information about "No Scopes" from here:
https://swagger.io/docs/specification/authentication/oauth2/
together with the information from the commit-message, we can change our example like following:
app.yaml:
security:
# enable authentication and require the "uid" scope for this endpoint
- oauth2: []
securityDefinitions:
oauth2:
type: oauth2
x-tokenInfoUrl: https://<my_idp>/<some_paths>/userinfo
scopes: {}
And instead of using "user", we use "token_info" in our function:
app.py:
get_secret(token_info):
return token_info
This way, I got all the information that was passed by our identity-provider.
Thank you all for thinking with me :) Hope, you find this useful.

What scope is required to call Executionhistory - List

I am creating a dashboard extension, and would like to call Executionhistory - List.
Currently, the extension manifest specifies only the scope vso.serviceendpoint_manage.
I get my authorization token via
const token = await VSS.getAccessToken();
var authHeader = VSS_Auth_Service.authTokenManager.getAuthorizationHeader(token);
I am able to successfully get a list of all service endpoints by using Endpoints - Get Service Endpoints
However, when I try to get the Execution History for a given Service Endpoint, I get a CORS policy error.
Access to {devops rest api} from origin {marketplace url} has been blocked by CORS policy
I know CORS errors can occur if the PAT does not have the correct scopes see here.
Note: When I try this locally with a PAT with all scopes, this call works. So I am fairly certain I am missing an extension scope.
Which scope am I missing?
To get service endpoint execution history, no need to set anything other, just set PAT token scope as Read & manage to Tokens:
You will see it is work to run this Executionhistory - List api.

Can't deploy an app to Intune store via graph API - DeviceManagementApps.ReadWrite.All is an invalid scope?

We want to enable uploading apps to the Intune store via an API.
I saw this example on GitHub, and want to do something similar in JS, so I've tried using the same REST calls.
The problem is, I can't seem to make the https://graph.microsoft.com/beta/deviceAppManagement/mobileApps request properly - I always get 401. When making the same request via the Graph API Explorer it works fine.
I tried fixing my permissions, and I'm kinda stuck getting the correct token.
I did the following steps with an admin account, on both the "common" and our own tennant:
Called the admin consent - https://login.microsoftonline.com/nativeflow.onmicrosoft.com/adminconsent?client_id=<ID>&redirect_uri=<URI>
Got authorization from the user - https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=<ID>&response_type=code&redirect_uri=<URI>&response_mode=query&scope=DeviceManagementApps.ReadWrite.All
POST request to get the actual token -
https://login.microsoftonline.com/nativeflow.onmicrosoft.com/oauth2/v2.0/token
with the following body:
client_id: <ID>
scope: https://graph.microsoft.com/.default
client_secret: <secret>
grant_type: client_credentials
requested_token_use: on_behalf_of
code: <The code I got in step 2>
I tried changing the scope in step 3 to https://graph.microsoft.com/DeviceManagementApps.ReadWrite.All or simply to DeviceManagementApps.ReadWrite.All, but it says that it's not a valid scope.
I got a token in step 3, but when I try calling the actual API I receive this error:
{
ErrorCode:"Forbidden",
Message:{
_version: 3,
Message: "An error has occurred - Operation ID (for customer support): 00000000-0000-0000-0000-000000000000 - Activity ID: 7b5c3841-976d-4509-b946-f7fdabd047d7 - Url: https://fef.msub02.manage.microsoft.com/StatelessAppMetadataFEService/deviceAppManagement/mobileApps?api-version=5018-05-02",
CustomApiErrorPhrase: "",
RetryAfter: null,
ErrorSourceService: "",
HttpHeaders: {"WWW-Authenticate":"Bearer realm=urn:intune:service,f0f3c450-59bf-4f0d-b1b2-0ef84ddfe3c7"}
},
Target:null,
Details:null,
InnerError:null,
InstanceAnnotations:[]
}
So yeah, I'm pretty much stuck. Anyone have any experience with it? I've tried making the calls in Postman, curl and via code, but nothing works.
Cheers :)
You have a couple issues going on:
You're using the Authorization Code Grant workflow but requesting Client Credentials.
The scope Device.ReadWrite.All is an application scope, it is only applicable to Client Credentials. It isn't a valid Delegated scope so it will return an error when you attempt to authenticate a user (aka delegate) using Device.ReadWrite.All.
Your body is using key:value but it should be using standard form encoding (key=value).
To get this working, you need to request a token without a user. This is done by skipping your 2nd step and moving directly to retrieving a token (body line-breaks are only for readability):
POST https://login.microsoftonline.com/nativeflow.onmicrosoft.com/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={id}
&client_secret={secret}
&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&grant_type=client_credentials

Get call with Authorization-header converts to OPTIONS call

I am using swagger-ui to create and test my apis calls but in case where I have a GET call with Authorization header it fails and returns ERROR. When I try to debug I found out that it sends this GET call as OPTIONS if Authorization header is present, else as a GET call.
The strange part is with Authorization header the POST call works fine.
/urlCode:
get:
description: Initiate something
parameters:
- name: Authorization
in: header
description: Authorization token
required: true
type: string
- name: var
in: query
description: variable
required: true
type: string
format: string
This is a CORS issue. To see what's allowed, web applications must first send an OPTIONS request prior to submitting the actual request (which is why you see OPTIONS and not GET). In order for it to work properly, you must enable CORS on the API itself. Further details can be found in swagger-ui's repository.

Resources