ansible/test/integration/targets/win_credential/library/test_cred_facts.ps1
Jordan Borean 8e92cca139
win_credential: new module to manage credentials (#48840)
* win_credential_manager: new module to manage credentials

* fix sanity issues and removed CredSSP references

* renamed module to win_credential

* fix typo on test variable

* fix sanity ignore line
2018-11-22 06:55:48 +10:00

498 lines
20 KiB
PowerShell

#!powershell
# Copyright: (c) 2018, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.AddType
$spec = @{
options = @{
name = @{ type = "str"; required = $true }
type = @{ type = "str"; required = $true; choices = @("domain_password", "domain_certificate", "generic_password", "generic_certificate") }
}
supports_check_mode = $true
}
$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec)
$name = $module.Params.name
$type = $module.Params.type
Add-CSharpType -AnsibleModule $module -References @'
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
namespace Ansible.CredentialManager
{
internal class NativeHelpers
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class CREDENTIAL
{
public CredentialFlags Flags;
public CredentialType Type;
[MarshalAs(UnmanagedType.LPWStr)] public string TargetName;
[MarshalAs(UnmanagedType.LPWStr)] public string Comment;
public FILETIME LastWritten;
public UInt32 CredentialBlobSize;
public IntPtr CredentialBlob;
public CredentialPersist Persist;
public UInt32 AttributeCount;
public IntPtr Attributes;
[MarshalAs(UnmanagedType.LPWStr)] public string TargetAlias;
[MarshalAs(UnmanagedType.LPWStr)] public string UserName;
public static explicit operator Credential(CREDENTIAL v)
{
byte[] secret = new byte[(int)v.CredentialBlobSize];
if (v.CredentialBlob != IntPtr.Zero)
Marshal.Copy(v.CredentialBlob, secret, 0, secret.Length);
List<CredentialAttribute> attributes = new List<CredentialAttribute>();
if (v.AttributeCount > 0)
{
CREDENTIAL_ATTRIBUTE[] rawAttributes = new CREDENTIAL_ATTRIBUTE[v.AttributeCount];
Credential.PtrToStructureArray(rawAttributes, v.Attributes);
attributes = rawAttributes.Select(x => (CredentialAttribute)x).ToList();
}
string userName = v.UserName;
if (v.Type == CredentialType.DomainCertificate || v.Type == CredentialType.GenericCertificate)
userName = Credential.UnmarshalCertificateCredential(userName);
return new Credential
{
Type = v.Type,
TargetName = v.TargetName,
Comment = v.Comment,
LastWritten = (DateTimeOffset)v.LastWritten,
Secret = secret,
Persist = v.Persist,
Attributes = attributes,
TargetAlias = v.TargetAlias,
UserName = userName,
Loaded = true,
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct CREDENTIAL_ATTRIBUTE
{
[MarshalAs(UnmanagedType.LPWStr)] public string Keyword;
public UInt32 Flags; // Set to 0 and is reserved
public UInt32 ValueSize;
public IntPtr Value;
public static explicit operator CredentialAttribute(CREDENTIAL_ATTRIBUTE v)
{
byte[] value = new byte[v.ValueSize];
Marshal.Copy(v.Value, value, 0, (int)v.ValueSize);
return new CredentialAttribute
{
Keyword = v.Keyword,
Flags = v.Flags,
Value = value,
};
}
}
[StructLayout(LayoutKind.Sequential)]
public struct FILETIME
{
internal UInt32 dwLowDateTime;
internal UInt32 dwHighDateTime;
public static implicit operator long(FILETIME v) { return ((long)v.dwHighDateTime << 32) + v.dwLowDateTime; }
public static explicit operator DateTimeOffset(FILETIME v) { return DateTimeOffset.FromFileTime(v); }
public static explicit operator FILETIME(DateTimeOffset v)
{
return new FILETIME()
{
dwLowDateTime = (UInt32)v.ToFileTime(),
dwHighDateTime = ((UInt32)v.ToFileTime() >> 32),
};
}
}
[Flags]
public enum CredentialCreateFlags : uint
{
PreserveCredentialBlob = 1,
}
[Flags]
public enum CredentialFlags
{
None = 0,
PromptNow = 2,
UsernameTarget = 4,
}
public enum CredMarshalType : uint
{
CertCredential = 1,
UsernameTargetCredential,
BinaryBlobCredential,
UsernameForPackedCredential,
BinaryBlobForSystem,
}
}
internal class NativeMethods
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredDeleteW(
[MarshalAs(UnmanagedType.LPWStr)] string TargetName,
CredentialType Type,
UInt32 Flags);
[DllImport("advapi32.dll")]
public static extern void CredFree(
IntPtr Buffer);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredMarshalCredentialW(
NativeHelpers.CredMarshalType CredType,
SafeMemoryBuffer Credential,
out SafeCredentialBuffer MarshaledCredential);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredReadW(
[MarshalAs(UnmanagedType.LPWStr)] string TargetName,
CredentialType Type,
UInt32 Flags,
out SafeCredentialBuffer Credential);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredUnmarshalCredentialW(
[MarshalAs(UnmanagedType.LPWStr)] string MarshaledCredential,
out NativeHelpers.CredMarshalType CredType,
out SafeCredentialBuffer Credential);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool CredWriteW(
NativeHelpers.CREDENTIAL Credential,
NativeHelpers.CredentialCreateFlags Flags);
}
internal class SafeCredentialBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeCredentialBuffer() : base(true) { }
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
NativeMethods.CredFree(handle);
return true;
}
}
internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeMemoryBuffer() : base(true) { }
public SafeMemoryBuffer(int cb) : base(true)
{
base.SetHandle(Marshal.AllocHGlobal(cb));
}
public SafeMemoryBuffer(IntPtr handle) : base(true)
{
base.SetHandle(handle);
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
}
public class Win32Exception : System.ComponentModel.Win32Exception
{
private string _exception_msg;
public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { }
public Win32Exception(int errorCode, string message) : base(errorCode)
{
_exception_msg = String.Format("{0} - {1} (Win32 Error Code {2}: 0x{3})", message, base.Message, errorCode, errorCode.ToString("X8"));
}
public override string Message { get { return _exception_msg; } }
public static explicit operator Win32Exception(string message) { return new Win32Exception(message); }
}
public enum CredentialPersist
{
Session = 1,
LocalMachine = 2,
Enterprise = 3,
}
public enum CredentialType
{
Generic = 1,
DomainPassword = 2,
DomainCertificate = 3,
DomainVisiblePassword = 4,
GenericCertificate = 5,
DomainExtended = 6,
Maximum = 7,
MaximumEx = 1007,
}
public class CredentialAttribute
{
public string Keyword;
public UInt32 Flags;
public byte[] Value;
}
public class Credential
{
public CredentialType Type;
public string TargetName;
public string Comment;
public DateTimeOffset LastWritten;
public byte[] Secret;
public CredentialPersist Persist;
public List<CredentialAttribute> Attributes = new List<CredentialAttribute>();
public string TargetAlias;
public string UserName;
// Used to track whether the credential has been loaded into the store or not
public bool Loaded { get; internal set; }
public void Delete()
{
if (!Loaded)
return;
if (!NativeMethods.CredDeleteW(TargetName, Type, 0))
throw new Win32Exception(String.Format("CredDeleteW({0}) failed", TargetName));
Loaded = false;
}
public void Write(bool preserveExisting)
{
string userName = UserName;
// Convert the certificate thumbprint to the string expected
if (Type == CredentialType.DomainCertificate || Type == CredentialType.GenericCertificate)
userName = Credential.MarshalCertificateCredential(userName);
NativeHelpers.CREDENTIAL credential = new NativeHelpers.CREDENTIAL
{
Flags = NativeHelpers.CredentialFlags.None,
Type = Type,
TargetName = TargetName,
Comment = Comment,
LastWritten = new NativeHelpers.FILETIME(),
CredentialBlobSize = (UInt32)(Secret == null ? 0 : Secret.Length),
CredentialBlob = IntPtr.Zero, // Must be allocated and freed outside of this to ensure no memory leaks
Persist = Persist,
AttributeCount = (UInt32)(Attributes.Count),
Attributes = IntPtr.Zero, // Attributes must be allocated and freed outside of this to ensure no memory leaks
TargetAlias = TargetAlias,
UserName = userName,
};
using (SafeMemoryBuffer credentialBlob = new SafeMemoryBuffer((int)credential.CredentialBlobSize))
{
if (Secret != null)
Marshal.Copy(Secret, 0, credentialBlob.DangerousGetHandle(), Secret.Length);
credential.CredentialBlob = credentialBlob.DangerousGetHandle();
// Store the CREDENTIAL_ATTRIBUTE value in a safe memory buffer and make sure we dispose in all cases
List<SafeMemoryBuffer> attributeBuffers = new List<SafeMemoryBuffer>();
try
{
int attributeLength = Attributes.Sum(a => Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE)));
byte[] attributeBytes = new byte[attributeLength];
int offset = 0;
foreach (CredentialAttribute attribute in Attributes)
{
SafeMemoryBuffer attributeBuffer = new SafeMemoryBuffer(attribute.Value.Length);
attributeBuffers.Add(attributeBuffer);
if (attribute.Value != null)
Marshal.Copy(attribute.Value, 0, attributeBuffer.DangerousGetHandle(), attribute.Value.Length);
NativeHelpers.CREDENTIAL_ATTRIBUTE credentialAttribute = new NativeHelpers.CREDENTIAL_ATTRIBUTE
{
Keyword = attribute.Keyword,
Flags = attribute.Flags,
ValueSize = (UInt32)(attribute.Value == null ? 0 : attribute.Value.Length),
Value = attributeBuffer.DangerousGetHandle(),
};
int attributeStructLength = Marshal.SizeOf(typeof(NativeHelpers.CREDENTIAL_ATTRIBUTE));
byte[] attrBytes = new byte[attributeStructLength];
using (SafeMemoryBuffer tempBuffer = new SafeMemoryBuffer(attributeStructLength))
{
Marshal.StructureToPtr(credentialAttribute, tempBuffer.DangerousGetHandle(), false);
Marshal.Copy(tempBuffer.DangerousGetHandle(), attrBytes, 0, attributeStructLength);
}
Buffer.BlockCopy(attrBytes, 0, attributeBytes, offset, attributeStructLength);
offset += attributeStructLength;
}
using (SafeMemoryBuffer attributes = new SafeMemoryBuffer(attributeBytes.Length))
{
if (attributeBytes.Length != 0)
Marshal.Copy(attributeBytes, 0, attributes.DangerousGetHandle(), attributeBytes.Length);
credential.Attributes = attributes.DangerousGetHandle();
NativeHelpers.CredentialCreateFlags createFlags = 0;
if (preserveExisting)
createFlags |= NativeHelpers.CredentialCreateFlags.PreserveCredentialBlob;
if (!NativeMethods.CredWriteW(credential, createFlags))
throw new Win32Exception(String.Format("CredWriteW({0}) failed", TargetName));
}
}
finally
{
foreach (SafeMemoryBuffer attributeBuffer in attributeBuffers)
attributeBuffer.Dispose();
}
}
Loaded = true;
}
public static Credential GetCredential(string target, CredentialType type)
{
SafeCredentialBuffer buffer;
if (!NativeMethods.CredReadW(target, type, 0, out buffer))
{
int lastErr = Marshal.GetLastWin32Error();
// Not running with CredSSP or Become so cannot manage the user's credentials
if (lastErr == 0x00000520) // ERROR_NO_SUCH_LOGON_SESSION
throw new InvalidOperationException("Failed to access the user's credential store, run the module with become or CredSSP");
else if (lastErr == 0x00000490) // ERROR_NOT_FOUND
return null;
throw new Win32Exception(lastErr, "CredEnumerateW() failed");
}
using (buffer)
{
NativeHelpers.CREDENTIAL credential = (NativeHelpers.CREDENTIAL)Marshal.PtrToStructure(
buffer.DangerousGetHandle(), typeof(NativeHelpers.CREDENTIAL));
return (Credential)credential;
}
}
public static string MarshalCertificateCredential(string thumbprint)
{
// CredWriteW requires the UserName field to be the value of CredMarshalCredentialW() when writting a
// certificate auth. This converts the UserName property to the format required.
// While CERT_CREDENTIAL_INFO is the correct structure, we manually marshal the data in order to
// support different cert hash lengths in the future.
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_cert_credential_info
int hexLength = thumbprint.Length;
byte[] credInfo = new byte[sizeof(UInt32) + (hexLength / 2)];
// First field is cbSize which is a UInt32 value denoting the size of the total structure
Array.Copy(BitConverter.GetBytes((UInt32)credInfo.Length), credInfo, sizeof(UInt32));
// Now copy the byte representation of the thumbprint to the rest of the struct bytes
for (int i = 0; i < hexLength; i += 2)
credInfo[sizeof(UInt32) + (i / 2)] = Convert.ToByte(thumbprint.Substring(i, 2), 16);
IntPtr pCredInfo = Marshal.AllocHGlobal(credInfo.Length);
Marshal.Copy(credInfo, 0, pCredInfo, credInfo.Length);
SafeMemoryBuffer pCredential = new SafeMemoryBuffer(pCredInfo);
NativeHelpers.CredMarshalType marshalType = NativeHelpers.CredMarshalType.CertCredential;
using (pCredential)
{
SafeCredentialBuffer marshaledCredential;
if (!NativeMethods.CredMarshalCredentialW(marshalType, pCredential, out marshaledCredential))
throw new Win32Exception("CredMarshalCredentialW() failed");
using (marshaledCredential)
return Marshal.PtrToStringUni(marshaledCredential.DangerousGetHandle());
}
}
public static string UnmarshalCertificateCredential(string value)
{
NativeHelpers.CredMarshalType credType;
SafeCredentialBuffer pCredInfo;
if (!NativeMethods.CredUnmarshalCredentialW(value, out credType, out pCredInfo))
throw new Win32Exception("CredUnmarshalCredentialW() failed");
using (pCredInfo)
{
if (credType != NativeHelpers.CredMarshalType.CertCredential)
throw new InvalidOperationException(String.Format("Expected unmarshalled cred type of CertCredential, received {0}", credType));
byte[] structSizeBytes = new byte[sizeof(UInt32)];
Marshal.Copy(pCredInfo.DangerousGetHandle(), structSizeBytes, 0, sizeof(UInt32));
UInt32 structSize = BitConverter.ToUInt32(structSizeBytes, 0);
byte[] certInfoBytes = new byte[structSize];
Marshal.Copy(pCredInfo.DangerousGetHandle(), certInfoBytes, 0, certInfoBytes.Length);
StringBuilder hex = new StringBuilder((certInfoBytes.Length - sizeof(UInt32)) * 2);
for (int i = 4; i < certInfoBytes.Length; i++)
hex.AppendFormat("{0:x2}", certInfoBytes[i]);
return hex.ToString().ToUpperInvariant();
}
}
internal static void PtrToStructureArray<T>(T[] array, IntPtr ptr)
{
IntPtr ptrOffset = ptr;
for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T))))
array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T));
}
}
}
'@
$type = switch ($type) {
"domain_password" { [Ansible.CredentialManager.CredentialType]::DomainPassword }
"domain_certificate" { [Ansible.CredentialManager.CredentialType]::DomainCertificate }
"generic_password" { [Ansible.CredentialManager.CredentialType]::Generic }
"generic_certificate" { [Ansible.CredentialManager.CredentialType]::GenericCertificate }
}
$credential = [Ansible.CredentialManager.Credential]::GetCredential($name, $type)
if ($null -ne $credential) {
$module.Result.exists = $true
$module.Result.alias = $credential.TargetAlias
$module.Result.attributes = [System.Collections.ArrayList]@()
$module.Result.comment = $credential.Comment
$module.Result.name = $credential.TargetName
$module.Result.persistence = $credential.Persist.ToString()
$module.Result.type = $credential.Type.ToString()
$module.Result.username = $credential.UserName
if ($null -ne $credential.Secret) {
$module.Result.secret = [System.Convert]::ToBase64String($credential.Secret)
} else {
$module.Result.secret = $null
}
foreach ($attribute in $credential.Attributes) {
$attribute_info = @{
name = $attribute.Keyword
}
if ($null -ne $attribute.Value) {
$attribute_info.data = [System.Convert]::ToBase64String($attribute.Value)
} else {
$attribute_info.data = $null
}
$module.Result.attributes.Add($attribute_info) > $null
}
} else {
$module.Result.exists = $false
}
$module.ExitJson()