by Dominick Baier
dbaier@leastprivilege.com
http://www.leastprivilege.com
@leastprivilege Principals & Identities
interface IIdentity
{
bool IsAuthenticated { get; }
string AuthenticationType { get; }
string Name { get; }
}
interface IPrincipal
{
IIdentity Identity { get; }
bool IsInRole(string roleName);
}
- Thread.CurrentPrincipal Every thread can have his own client security context Plumbing sets it, application gets it.
WindowsPrincipal
var id = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(id);
principal.IsInRole("Builtin\\Users") //Don't use because they are localized.
var localAdmins = new SecurityIdentifier(WellknownSidType.BuiltinAdministratorSid, null);
var domainAdmins = new SecurityIdentifier(WellknownSidType.AccountDomainAdminsSid, id.User.AccountDomainSid);
var users = new SecurityIdentifier(WellknownSidType.BuiltinUsersSid, null);
var account = new NTAccount(id.name);
var sid = account.Translate(typeof(SecurityIdentifier));
var groups = user.Groups.Translate(typeof(NTAccount));
GenericPrincipal
var roles = new string[] { "Sales", "Marketing" };
var p = new GenericPrincipal(new GenericIdentity("bob"), roles);
Thread.CurrentPrincipal = p;
p.Identity.Name
Role-based access control (RBAC)
if p.IsInRole("Sales") {} //returns true/false
new PrincipalPermission(null, "Development").Demand(); //will throw security exception if fails
[PrincipalPermission(SecurityAction.Demand, Role="Development"] //hard to unit test
private static void DoDevelopment() {}
- Claims are statements
- How do we handle things no longer in corporate network like cloud, partners and customers ?
- Bell-Lapadula Model for goverment and military document security.
2002 Identity
2006 WCF System.IdentityModel with SecurityToken
2009 WIF Microsoft.IdentityModel with IClaimsIdentity & IClaimsPrincipal
2012 .NET 4.5 System.IdentityModel & System.Security.Claims eg. Bob is an administrator, Jim's email address is jim@foo.com, ...
public class Claim
{
public virtual string Type { get; }
public virtual string Value { get; }
public virtual string Issuer { get; }
//...
}
class ClaimsIdentity : IIdentity
{
IEnumerable<Claim> Claims { get; }
}
class ClaimsPrincipal : IPrincipal
{
ReadOnlyCollection<ClaimsIdentity> Identities { get; }
}
var claim = new Claim("name", "dominick");
var claim = new Claim(ClaimTypes.Name, "dominick");
var Claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "dominick"),
new Claim(ClaimTypes.Email, "dominick@foo.com"),
new Claim(ClaimTypes.Role, "Geek"),
new Claim("http://myClaims/location", "Heidelberg")
};
var id = new ClaimsIdentity(claims);
id.IsAuthenticated -> false because you can add claims to anonymous
var id = new ClaimsIdentity(claims, "Console App", ClaimTypes.Name, ClaimTypes.Role); //what do name and isrole map too for legacy
id.IsAuthenticated -> true
var cp = new ClaimsPrincipal(id); //prefered entry point
Thread.CurrentPrincipal = cp;
-> var cp = ClaimsPrincipal.Current;
var email = cp.FindFirst(ClaimTypes.Email).Value;
RolePrincipal, GenericPrincipal, WindowsPrincipal : ClaimsPrincipal : IPrincipal
Generalization & Specialization Interface level: IIdentity : Name, AuthenticationType, IsAuthenticated
Claims identity: ClaimsIdentity : Claims, FindAll(), FindFirst(), HasClaim()
Domain specific: WindowsIdentity : Token, Impersonate(), User/Device
- Claims Always try to use ClaimsIdentity for your custom principal implementation.
class CorpIdentity : ClaimsIdentity
{
public CorpIdentity(string name, string reportsTo, string office)
{
AddClaim(new Claim(ClaimTypes.Name, name));
AddClaim(new Claim("reportsto", reportsTo));
AddClaim(new Claim("office", office));
}
public string office
{
get { return FindFirst("reportsto").Value; }
}
}
- Services Unification of various credential formats to common ClaimsPrincipal representation Windows/Kerberos, Forms Authentication, HTTP basic authentication, SSL client certificates, WS-Security tokens, SAML, extensible, ...
- Processing Pipeline Request (xml/binary/text) -> Security token handler (seserialization/validation) -> Claims transformation (skipped when session available) -> Security Session Management -> Session Security Token -> Authorization
public class ClaimsTransformer : ClaimsAuthenticationManager
{
public overrride ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
var name = incomingPrincipal.Identity.Name;
if (string.IsNUllOrWhiteSpace(name)) throw new SecurityException("Name claim is missing");
if (incomingPrincipal.Identity.IsAuthenticated)
{
return TransformClaims(incomingPrincipal);
}
return incomingPrincipal;
}
}
<system.identityModel>
<identityConfiguration>
<claimsAuthenticationManger type="assembly/class" />
</identityConfiguration>
</system.identityModel>
var p = new WindowsPrincipal(WindowsIdentity.GetCurrent());
Thread.CurrentPrincipal = FederationAuthentication.FederationConfiguration.identityConfiguration.ClaimsAuthenticationManager.Authenticate("none", p) as IPrincipal;
- Session management Preserve a ClaimsPrincipal across round trips (cookies, ws-secureconversation)
var sessionToken = new SessionSecurityToken(principal, TimeSpan.FromHours(8));
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
- Data protection api, zero configuration but only for single server.
- For webfarms use machinekey or ssl-certificate to protect your cookie.
- Roundtrip the identity and cache the claims principal.
- Claims authorization manager Extensibility point for loading/parsing authorization policy
- Extensibility point for mapping operations/resources to required claims
- Auto-invoked during request processing
- Application code should not check for claims directly
public class AuthorizationContext
{
public AuthorizationContext();
public Collection<Claim> Action { get; }
public ClaimsPrincipal Principal { get; }
public Collection<Claim> Resource { get; }
}
<claimsAuthorizationManager type="ClaimsAuthorizationManagerClass, theAssembly" >
<policy file="foo.xml" />
</claimsAuthorizationManager>
public class ClaimAuthZManager : ClaimsAuthorizationManager
{
public override bool CheckAccess(AuthorizationContext context)
{
//inspect context and make authorization decision
var resource = context.Resource.First().Value;
var action = context.Action.First().Value;
if (action == "Show" && resource == "Castle")
{
var hasCastle = context.Principal.HasClaim("http://myclaims/hasCastle", "true");
return hasCastle;
}
return false;
}
override void LoadCustomConfiguration(XmlNodeList nodelist)
{
base.LoadCustomConfiguration(nodelist);
}
}
[ClaimsPrincipalPermission(SecurityAction.Demand, Operation = "Add", Resource = "Customer")]
public void AddCustomer(Customer customer) { ... }
void Print(Document document)
{
if (ClaimsPrincipalPermission.CheckAccess(document.Printer, "Print")) { ... }
}
var authZ = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.Claims.ClaimsAuthZManager;
authZ.CheckAccess(...);
http://msdn.microsoft.com/en-us/library/system.security.claims.claimsauthorizationmanager.aspx
Protocol support
- Web Application : WS-Federation
- SOAP : WS-Trust & WS-Security
- WebApi : OAuth2 Client -> STS -> Token -> Client -> Token -> Relying Party/Application (no authentication on RP) <saml:Assertion ... Signature ...> tokens for seamless third party authentication
Security Token Services
- Microsoft Active Directory
- Federation Service 2
- IBM Tivoli Federation Manager
- Oracle Identity Manager
- Ping Federate
- Thinktecture .NET 4.5 (http://identityserver.codeplex.com/) ASP.Net
<authentication mode="windows">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
<system.identityModel configSource="identity.config" />
<system.identityModel>
<identityConfiguration>
<audienceUris />>
<claimsAuthenticationManager type="Security.ClaimsTransformer, Web" />
<issuerNameRegistry />
</identityConfiguration>
</system.identityModel>
private void EstablishSession(ClaimsPrincipal principal)
{
if (HttpContext.Current != null)
{
var sessionToken = new SessionSecurityToken(principal);
FederatedAuthentication.SessionAuthenticationModule.WriteSessionTokenToCookie(sessionToken);
}
}
<system.webServer>
<add name="ClaimsTransformationModule" type="Security.ClaimsTransformationHttpModule" />
<add name="SessionAuthenticationModule" type="Security.IdentityModel.Services.SessionAuthenticationModule, ..." />
</system.webServer>
<system.identityModel.services configSource="identity.Services.config" />
<system.identityModel.services>
<federationConfiguration>
<wsFederation passiveRedirectionEnable="true" issuer="remote login page location"
realm="" requireHttps="true" />
<cookieHandler requireSsl="true" />
</federationConfiguration>
</system.identityModel>
for wcf: <bindings> <ws2007FederationHttpBinding>
webapi
[Authorize]
public class IdentityController : ApiController
{
public Identity Get()
{
return new Identity(User.Identity);
}
}