This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Best Practices / Approaches - PAM - automatic change of service user passwords - how to update sync projects

Hi Experts

While discussing this on a customer project, i'm reaching out to get some best practices and / or approaches for the following situation: the customer does have a PAM solution in place which is changing service user passwords automatically. Now we do have a situation, where the user leveraged for the AD-sync project did get a new password through the PAM solution and in result of that was unable to connect to the AD anymore.

What would be best practices or good approaches to let the PAM solution update the user password in 1IM sync projects (versions > 7.0)? Are there any running implementations out there that you would be able to share experiences?

Thanks
Carsten

  • Hi Carsten,

    I suggest using scripted variables in your Sync Project to fetch the current password from the PAM solution during the connection phase of each sync operation.

    To secure the fetch, your PAM solution hopefully supports a certificate-based authentication.

    If used this for a showcase with our own One Identity SafeGuard PAM solution.

  • Thanks for sharing this approach Markus. Are there any code examples with regards to SafeGuard you could share?
  • Hi Carsten,

    the attached code was used in a scripted variable to use a cert-based user in One Identity SafeGuard 1.0 to fetch the password for the AD user used in the AD sync project.

    You need to have a decent RestSharp.dll present in your One Identity Manager directory like the one that the DGE module brings with its installation lately (Version 105.1 or higher).

    4024.SafeGuard 1.0 Get Password Variable Script.vb.txt
    References RestSharp.dll
    References NewtonSoft.Json.dll
    Imports System.Security.Cryptography.X509Certificates
    Imports NewtonSoft.Json.Linq
    Imports RestSharp
    
    
    ' DO NOT USE THAT IN PRODUCTION 
    ' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    ' Delegate to allow all SSL certificates (Including self-signed)
    System.Net.ServicePointManager.ServerCertificateValidationCallback =
            Function(se As Object,
            cert As X509Certificate,
            chain As X509Chain,
            sslerror As System.Net.Security.SslPolicyErrors) True
    
    Dim pwRetrievalSuccesfull As Boolean = False
    Dim pw As String = String.Empty
    Dim requestID As String = String.Empty
    Dim accountID As String = String.Empty
    Dim accessToken As String = String.Empty
    Dim response As IRestResponse
    Dim responseArray As JArray
    Dim responseObj As JObject
    Dim body As JObject
    Dim pwWithErrorStatus As KeyValuePair(Of String, Boolean)
    Dim certCol As New X509CertificateCollection()
    
    ' Getting the account name 
    ' - for which the password Is fetched from SafeGuard - 
    ' from another variable in the "currently used" variable set
    Dim accountName As String = VariableSet("CP_d1imloginaccount", "")
    
    ' Set the SafeGuard URI configurable by ConfigParm
    Dim SafeGuardUrl As String = args.QueryDatabase(Connection.SystemQuery.From("DialogConfigParm").Select("Value").Filter("Fullpath='Custom\SafeGuard\URL'")).Result.First.GetValue("Value").AsString
    
    ' Set the thumbprint for the Client Credentials used to connect to SafeGuard configurable by ConfigParm
    Dim thumbprint As String = args.QueryDatabase(Connection.SystemQuery.From("DialogConfigParm").Select("Value").Filter("Fullpath='Custom\SafeGuard\ThumbPrint'")).Result.First.GetValue("Value").AsString
    
    ' Fetch the certificate from the local CERT store used to access SafeGuard
    Dim store As X509Store = New X509Store(StoreName.My, StoreLocation.CurrentUser)
    store.Open(OpenFlags.ReadOnly)
    ' Assign the certificate to the RestSharp Client
    certCol = New X509CertificateCollection(store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, False))
    store.Close()
    
    ' Create the RestSharp Client to access SafeGuard
    Dim client As RestClient = New RestClient(SafeGuardUrl) With {.CookieContainer = New Net.CookieContainer(), .ClientCertificates = certCol}
    
    ' Define functions für web requests
    Dim GetAccessToken = Function() As String
                                ' Get the access token from SafeGuard using client credentials (certificate)
                                Dim _request = New RestRequest("RSTS/oauth2/token", Method.POST) With {.RequestFormat = DataFormat.Json}
                                '_request.AddHeader("Content-Type", "application/json")
                                body = New JObject() From {{"grant_type", "client_credentials"}, {"scope", "dell:sts:primaryproviderid:certificate"}}
                                _request.AddParameter("application/json", body.ToStringNullSafe(), RestSharp.ParameterType.RequestBody)
                                response = client.Execute(_request)
                                ' Check if request for the access token was successfull
                                If response.StatusCode = Net.HttpStatusCode.OK Then
                                    ' Extract Access Token
                                    responseObj = JObject.Parse(response.Content)
                                    accessToken = responseObj.SelectToken("access_token").ToStringNullSafe()
                                    Return accessToken
                                Else
                                    Return String.Empty
                                End If
                            End Function
    
    Dim FetchAccountID = Function(ByVal _AccountName As String) As String
                                ' Fetch id for account from SafeGuard
                                Dim _request As RestRequest = New RestRequest("service/core/v1/Me/RequestableAccounts", Method.GET) With {.RequestFormat = DataFormat.Json}
                                _request.AddParameter("Authorization", String.Format("Bearer {0}", accessToken), RestSharp.ParameterType.HttpHeader)
                                ' Add filter for accountname
                                ' Simple AD Specific Filter for the account name splitting by first \
                                Dim _accountInfo() As String = Split(_AccountName, "\", 2)
                                Select Case _accountInfo.Count()
                                    Case 1
                                        _request.AddQueryParameter("filter", String.Format("Name eq '{0}'", _accountInfo(0)))
                                    Case 2
                                        _request.AddQueryParameter("filter", String.Format("Name eq '{0}' and (NetBiosName eq '{1}' or DomainName eq '{1}')", _accountInfo(1), _accountInfo(0)))
                                    Case Else
                                    _request.AddQueryParameter("filter", String.Format("Name eq '{0}'", _AccountName))
                                End Select
    
                                response = client.Execute(_request)
                                ' Check if request for account to retrieve password for was successfull
                                If response.StatusCode = Net.HttpStatusCode.OK Then
                                    ' Extract id from response
                                    responseArray = JArray.Parse(response.Content)
                                    ' Check if only one account was retrieved
                                    If responseArray.Count = 1 Then
                                        Return responseArray.SelectToken("[0].Id").ToStringNullSafe()
                                    End If
                                End If
                                Return String.Empty
                            End Function
    
    Dim CreateNewPasswordRequest = Function(ByVal _accountID As String) As String
                                        ' Create new password request
                                        Dim _request As RestRequest = New RestRequest("service/core/v1/PasswordRequests", Method.POST) With {.RequestFormat = DataFormat.Json}
                                        _request.AddParameter("Authorization", String.Format("Bearer {0}", accessToken), RestSharp.ParameterType.HttpHeader)
                                        ' Create body for password request
                                        body = New JObject() From {
                                            {"AccountId", _accountID},
                                            {"IsEmergency", True},
                                            {"ReasonCodeId", ""},
                                            {"ReasonComment", "One Identity Manager Sync Password Checkout"},
                                            {"RequestedDurationDays", ""},
                                            {"RequestedDurationHours", ""},
                                            {"RequestedDurationMinutes", ""},
                                            {"RequestedFor", ""},
                                            {"TicketNumber", ""}
                                        }
                                        _request.AddParameter("application/json", body.ToStringNullSafe(), RestSharp.ParameterType.RequestBody)
                                        response = client.Execute(_request)
                                        ' Check if request was successfully created
                                        If response.StatusCode = Net.HttpStatusCode.Created Then
                                            responseObj = JObject.Parse(response.Content)
                                            ' Extract id from response
                                            Return responseObj.SelectToken("Id").ToStringNullSafe()
                                        Else
                                            Throw New Exception(response.Content)
                                        End If
                                    End Function
    
    Dim CheckoutPasswordForRequestID = Function(ByVal _requestID As String) As KeyValuePair(Of String, Boolean)
                                            ' Checkout password for request ID
                                            Dim _request As RestRequest = New RestRequest("service/core/v1/PasswordRequests/{passwordRequestId}/CheckOutPassword", Method.POST) With {.RequestFormat = DataFormat.Json}
                                            _request.AddParameter("Authorization", String.Format("Bearer {0}", accessToken), RestSharp.ParameterType.HttpHeader)
                                            _request.AddUrlSegment("passwordRequestId", _requestID)
                                            response = client.Execute(_request)
                                            ' Check if password could be fetched
                                            Select Case response.StatusCode
                                                Case Net.HttpStatusCode.OK
                                                    Return New KeyValuePair(Of String, Boolean)(JToken.Parse(response.Content).ToStringNullSafe(), False)
                                                Case Net.HttpStatusCode.BadRequest
                                                    ' Check if request has been expired or if the password has been checked-in in the meantime as reson for BadRequest
                                                    If JToken.Parse(response.Content)("Code").ToStringNullSafe() = "90405" Then
                                                        Return New KeyValuePair(Of String, Boolean)(String.Empty, True)
                                                    Else
                                                        ' Throw exception in all other cases
                                                        Throw New Exception(response.Content)
                                                    End If
                                                Case Else
                                                    Throw New Exception(response.Content)
                                            End Select
                                        End Function
    
    Dim AcknowledgePasswordRequest = Function(ByVal _requestID As String) As Boolean
                                            ' Acknowledge password request
                                            Dim _request As RestRequest = New RestRequest("service/core/v1/PasswordRequests/{passwordRequestId}/Acknowledge", Method.POST) With {.RequestFormat = DataFormat.Json}
                                            _request.AddParameter("Authorization", String.Format("Bearer {0}", accessToken), RestSharp.ParameterType.HttpHeader)
                                            _request.AddUrlSegment("passwordRequestId", _requestID)
                                            response = client.Execute(_request)
                                            ' Check if request could be acknowledged
                                            If response.StatusCode = Net.HttpStatusCode.OK Then
                                                Return True
                                            Else
                                                Throw New Exception(response.Content)
                                            End If
                                        End Function
    
    Dim FetchExistingValidRequest = Function(ByVal _accountId As String) As String
                                        ' Check if password request for this account already exists
                                        Dim _request As RestRequest = New RestRequest("service/core/v1/PasswordRequests", Method.GET) With {.RequestFormat = DataFormat.Json}
                                        _request.AddParameter("Authorization", String.Format("Bearer {0}", accessToken), RestSharp.ParameterType.HttpHeader)
                                        Try
                                            response = client.Execute(_request)
                                            ' Check if list of current requests could be fetched
                                            If response.StatusCode = Net.HttpStatusCode.OK Then
                                                responseArray = JArray.Parse(response.Content)
                                                responseObj = responseArray.Children(Of JObject)().FirstOrDefault(Function(o) o("AccountId").ToString() = _accountId)
                                                ' Check if password request could be extracted from response
                                                If Not responseObj Is Nothing Then
                                                    ' Check if request needs to be acknowledged (because of expiration or revokation)
                                                    If responseObj.SelectToken("NeedsAcknowledgement").ToObject(Of Boolean) Then
                                                        AcknowledgePasswordRequest(responseObj.SelectToken("Id").ToStringNullSafe())
                                                        Return String.Empty
                                                    Else
                                                        Return responseObj.SelectToken("Id").ToStringNullSafe()
                                                    End If
    
                                                Else
                                                    Return String.Empty
                                                End If
                                            Else
                                                Throw New Exception(response.Content)
                                            End If
                                        Catch e As Exception
                                            Throw New Exception(e.Message)
                                        End Try
    
    
                                    End Function
    
    ' Get the access token from SafeGuard using client credentials (certificate)
    accessToken = GetAccessToken()
    ' Check if access token could be retrieved
    If Not String.IsNullOrEmpty(accessToken) Then
        ' Fetch accountID for accountName from SafeGuard
        accountID = FetchAccountID(accountName)
        ' Check if accountID could be fetched
        If Not String.IsNullOrEmpty(accountID) Then
            Dim intRetries As Integer = 3
            Do
                Try
                    ' Fetch existing accounts from same user for the accountID 
                    requestID = FetchExistingValidRequest(accountID)
                    ' If no request exists
                    If String.IsNullOrEmpty(requestID) Then
                        ' Create new password request for account
                        requestID = CreateNewPasswordRequest(accountID)
                    End If
                    ' Checkout password for request ID
                    pwWithErrorStatus = CheckoutPasswordForRequestID(requestID)
                    ' Check if password could be fetched
                    If Not pwWithErrorStatus.Value Then
                        pw = pwWithErrorStatus.Key
                        ' DO NOT UNCOMMENT THE NEXT LINE IN PRODUCTION
                        ' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                        'Log.Debug("Sync Password from SafeGuard:{0}", pw)
                        pwRetrievalSuccesfull = True
                    End If
                Catch e As Exception
                    Log.Debug(e.Message)
                End Try
                intRetries = intRetries - 1
            Loop Until pwRetrievalSuccesfull = True Or intRetries = 0
        End If
    End If
    
    If pwRetrievalSuccesfull Then
        Return pw
    Else
        Throw New Exception("Password could not be fetched from password vault.")
    End If