How can I create a service account user in Jenkins alongside other users from a Security Realm like SSO or LDAP? - jenkins

In my organisation, service account users cannot be added to our SSO identity provider. Is there a way I can add a user to Jenkins' system and then create a token for it?
There is no requirement for this user to actually be able to login.
The ability to make accounts alongside the security realm accounts appears to be a limitation in Jenkins: https://issues.jenkins.io/browse/JENKINS-38257

At least on our setup which uses a SAML security realm with Okta SSO - an admin can run the following from the script console:
import hudson.model.*
import jenkins.model.*
import jenkins.security.*
import jenkins.security.apitoken.*
def userName = 'service-account-name'
def tokenName = 'service-access-token'
def user = User.get(userName, true) // `true` will create the user if it does not exist.
def token = user.getProperty(ApiTokenProperty.class).tokenStore.generateNewToken(tokenName)
user.save()
return token.plainValue
The result will output the token that can be used in API requests. If the user already exists then it will add a new token to the user.
Any created or pre-existing token can be revoked from the user's page under People.

Related

Check OpenID Connect user against local web application users with OWIN

I need to add OpenID Connect authentication to an existing ASP.NET MVC web application. For that, I am using OWIN.
Once the user has been successfully authenticated by the authentication server (Azure Active Directory in my case), I need to check that the user can be mapped to a local account in my web application. If the user has no valid local account, the actual login process should fail. Is there a standard way to do that?
In the sample I have found (https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-asp-webapp), it seems that as soon as the user is successfully authenticated by Azure, then it is logged in the application. Can we add custom code to the OWIN-OIDC sign-in process to perform additional checks?
Thanks for your help.
Good question and there are 2 levels here, as you are finding:
Authenticating with the Authorization Server / Identity Provider
Access to the application being allowed
If the first is successful but the second is not, then the standard behaviour is to redirect the user to an 'Access Denied' page (this page must allow anonymous access, to avoid a redirect loop). The login has not failed, but the user is not authorized to access the app.
OWIN uses response_type='code id_token' meaning that you will have an Open Id Connect User Identity to work with. If you can't find the user in your own data you can perform a redirect in the below callback:
var openidOptions = new OpenIdConnectAuthenticationOptions
{
Notifications = new OpenIdConnectAuthenticationNotifications
{
// Point to a callback to check the identity against my own system
// This callback can perform a redirect if the app does not recognise the user
AuthorizationCodeReceived = HandleAuthorizationCodeReceived
}
}

Get Azure AD directory users in a Rails app

I have a Rails 6 application and I want to use Azure Active Directory as an authentication system (with open id connect, saml2 and ldap).
The authentication is done.
Now I am trying to display user information like names or email addresses. I also want to be able to export all users of a directory.
I have tried to set a configuration up like so:
In my Rails app, in the admin panel, an admin can configure Azure AD for my application
in the config, the admin copies and pastes the configuration link provided by Azure AD (a JSON response)
Then, copies and pastes the app client_id
Then, the tenant_id (directory id)
Here is a piece of code that I expected to work:
def update_oidc
identity_provider = IdentityProvider.find_by(provider_type: 'open_id_connect', id: params[:id])
client_id = params[:client_id].strip
metadata_url = params[:metadata_url].strip
tenant_id = params[:tenant_id].strip
metadata = HTTParty.get(metadata_url).parsed_response
identity_provider.update(config: {
metadata: metadata,
metadata_url: metadata_url,
client_id: client_id,
tenant_id: tenant_id,
})
if tenant_id
directory_access_url = "https://graph.windows.net/#{tenant_id}/users?api-version=1.6"
result = HTTParty.get(directory_access_url).parsed_response
identity_provider.directories.find_or_create_by(tenant_id: tenant_id).update(
business_phones: result["business_phones"],
display_name: result["display_name"],
given_name: result["given_name"],
job_title: result["job_title"],
email: result["user_principal_name"],
mobile_phone: result["mobile_phone"],
office_location: result["office_location"],
surname: result["surname"]
)
end
redirect_to identity_provider
end
As the tenant_id is the directory id, i thought that we might be able to access user info this way (and following the Microsoft Docs). The thing is, it doesn't work because even though I'm connected to my Azure AD directory in my app, when I run result = HTTParty.get(directory_access_url).parsed_response, i have an authentication error telling me the token has expired or that i need to be connected.
I don't want to use PowerShell or anything like this. I want to be able to access directories data through my app.
Can someone tell me what i'm doing wrong or come up with an idea ?
Thanks
Just according to your code, I think you want to get the collection of users via the Azure AD Graph REST API Get users using jnunemaker/httparty library.
However, it seems to be missing the required header Authorization with its value like Bearer eyJ0eX ... FWSXfwtQ as the section Authentication and authorization of the offical document Operations overview | Graph API concepts said. Meanwhile, you have done the authentication with OpenID Connect, but Azure AD Graph API requires the access token as Authorization value from OAuth2 as the content below said.
The Graph API performs authorization based on OAuth 2.0 permission scopes present in the token. For more information about the permission scopes that the Graph API exposes, see Graph API Permission Scopes.
In order for your app to authenticate with Azure AD and call the Graph API, you must add it to your tenant and configure it to require permissions (OAuth 2.0 permission scopes) for Windows Azure Active Directory. For information about adding and configuring an app, see Integrating Applications with Azure Active Directory.
Azure AD uses the OAuth 2.0 authentication protocol. You can learn more about OAuth 2.0 in Azure AD, including supported flows and access tokens in OAuth 2.0 in Azure AD.
So I'm afraid you have to get the access token manually via OAuth2 for Azure AD again for using Graph API, or just simply refer to the sample code samples/authorization_code_example/web_app.rb using the adal library of GitHub repo AzureAD/azure-activedirectory-library-for-ruby for Ruby.

Authenticating application using Cognito and Devise

I am trying to implement AWS Cognito into my application for better all round authentication. The system is a Rails application that is currently using Warden/Devise as the method for handling user accounts (Login,Registration).
My goal is to have a AWS UserPool that contains the list of users for the application. When a user is verified using Cognito I wish to then search the tables that we currently use for the role and move the user to the correct area of the system based on the role that they are assigned too.
I have started to implement the logic to handle this but have come up against a brick wall.
Please see below my code.
cognito_authenticatable.rb
Logic for handling the cognito authentication. All i want to do here is check that the user is registered and return the valid token so i can prefer internal application checks to gather the user role.
def authenticate!
if params[:login]
region_name = 'us-east-2'
user_pool_id = 'us-east-2_Qj78BNQon'
client_id = '1pv3eno72e51mll3q36cuiojmr'
client = Aws::CognitoIdentityProvider::Client.new(
region: region_name
)
resp = client.initiate_auth({
client_id: client_id,
auth_flow: "USER_PASSWORD_AUTH",
auth_parameters: {
"USERNAME" => email,
"PASSWORD" => password
}
})
end
end
divise.rb
This code is just to add the new authentication strategy to the applications warden service.
config.warden do |manager|
manager.strategies.add(:cognito,
Devise::Strategies::CognitoAuthenticatable)
manager.default_strategies(:scope => :login).unshift :cognito
manager.default_strategies(:scope => :login).pop
end
The output error within the console is
Aws::Errors::MissingCredentialsError (unable to sign request without credentials set):
config/initializers/cognito_authenticatable.rb:23:in `authenticate!'
and here is an image from the localhost application that was running.
Any help on this would be amazing.
Thanks in advance.
One solution could be to uncheck the option for generating a client secret when you create the app client in the Cognito user pool. This option is checked by default and you have to know to uncheck it (https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html).
By default, user pools generate a client secret for your app. If you don't want that to happen, clear Generate client secret.
It's only possible to uncheck the client secret during the creation of a new client, so you might have to delete your client app and create a new one (not a big deal).
I also collect my learnings on Cognito, Devise, Rails and VueJS in a Medium article: https://medium.com/#morgler/beta-learnings-from-developing-vuejs-quasar-aws-amplify-and-cognito-application-dd38ec58b881
You are getting this error due to your AWS SDK for Ruby not being configured correctly. That error would likely exist not only for Cognito APIs, but it would exist for any AWS Signature V4 signed API calls. Kindly refer to this documentation to configure your SDK correctly for your application.

Not able to extract access token google service account

I have a consumer google account of the form
"me#gmail.com" for which I have a service account of the form
"Something#developer.gserviceaccount.com". I am trying to use the private key generated for this service account to generate an access token and then may be edit or view the calendar associated with "me#gmail.com".
The authentication code:
String emailAddress = "something#developer.gserviceaccount.com";
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
File file = new File("path to .p12 file");
HttpTransport httpTransport = GoogleNetHttpTransport
.newTrustedTransport();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(emailAddress)
.setServiceAccountPrivateKeyFromP12File(file)
.setServiceAccountScopes(
Collections.singleton("https://www.googleapis.com/auth/calendar"))
.setServiceAccountUser("me#gmail.com")
.build();
String accessToken = credential.getAccessToken();
But the access token generated is null. The service account has edit permissions. The program is able to access the .p12 file.
Any cue as to where am I going wrong?
I think you've misunderstood how Service Accounts work. Impersonating a user only works within a Google Apps domain. You can't use a Service Account to impersonate a gmail account.
I doubt you get an access token when using a service account. If you were using OAuth2 dance and prompting the user for permissions then yes can get an access token, etc.. This is the correct way to initialize the API Calendar instance from a Google Credential object:
import com.google.api.services.calendar.Calendar;
Calendar service = new Calendar.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential).build();
You can then use the Calendar instance to make API calls. More information can be found here:
https://developers.google.com/admin-sdk/directory/v1/guides/delegation
https://developers.google.com/google-apps/calendar/quickstart/java

EmberJS, Rail and third-party OAuth2 login

Now I'm looking for more than two days for a solution for the following problem. I have an EmberJS client-side javascript app, that uses my server-side Rail RESTful API. Authorization is done via access token.
Now I want to give the user the ability also to login with OAuth2 from Google, Twitter and so on. Discourse uses OmniAuth for third party login, but OmniAuth needs a server side session. Because I build a RESTful and stateless API, I didn't want to use a session on the server. So I decide to build it on my own with help of Google+ Sign-In for server-side apps, but the example there also uses a session.
Does anyone have a solution for a similar problem or some hints for solving my problem?
EDIT 1
Because OmniAuth doesn't fit well in my setup, I started to create a own implementation for third-party OAuth2 login following Googles help. Everything works fine at the moment. But I didn't implement the CSRF protection explained under heading 1. Create an anti-forgery state token on the site from Google mentioned above. My problem is, how could I store this CSRF token without using a session. Would it be enough to store it in Database and look it up in the callback request from Google later?
EDIT 2
I followed this railscast. There three possible cases, if a user want to sign-in with an extern oauth provider:
The user already signed-up with extern oauth, then he got a Doorkeeper access token.
The user has an account, but didn't sign-in with extern provider before. After oauth flow we only have to create a new authentication for this user.
The user didn't have an account and now tries to sign-in with extern provider. Here we have to redirect the user to the sign-up page, we also can use informations from the oauth provider to pre-fill in the sign-up form, but until the user pushes the sign-up button, we have to save the authentication.
My question now is, what is a good practice to save such informations (authentication, oauth2 csrf-token) server-side in a REST API, without using a session. I have to save these information on the server, because the user should not have the possibility to manipulate them on the client-side.
Maybe I also should create a new question for pros and cons of token and session authentication with Ember apps and possible solutions for both?
Here is my authentication controller:
class Api::V1::AuthenticationsController < ApplicationController
def oauth
# redirect to google/twitter/...
login_at(params[:provider])
end
def callback
# callback from provider
provider = params[:provider]
if #user = login_from(provider)
doorkeepter_token = Doorkeeper::AccessToken.create!(:resource_owner_id => #user.id)
#data = {
access_token: doorkeepter_token.token,
user: #user
}
render 'oauth/complete'
else
# user has no account, create a new one
#user = User.new
#user.email = #user_hash[:user_info]['email']
#user.authentications.build(:uid => #user_hash[:uid], :provider => params[:provider])
#user.oauth_pending!
if #user.save
doorkeepter_token = Doorkeeper::AccessToken.create!(:resource_owner_id => #user.id)
#data = {
access_token: doorkeepter_token.token,
user: #user,
errors: #user.errors
}
render 'oauth/complete'
else
render 'oauth/error'
end
end
end
end
There's an example in the Ember.SimpleAuth repo that shows how to use Facebook as an external authentication provider: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-external-oauth/index.html. It basically does it the same way Discourse does it or was doing it 2-3 months ago (not sure whether they changed it) while it doesn't require a server side session.
Supporting Google would work basically the same way.
I updated the Facebook Auth example so that the server part is optional - it now uses the Facebook JS SDK: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-facebook-auth.html. Maybe that help you to get an understanding how Google Auth could be implemented - I'm sure it's going to work quite similarly.
Have you seen the ember-simple-auth project? It supports OAuth2.

Resources