protected CanAccessDatabase GetDatabaseAccessValidationFunc(RavenServer.AuthenticateConnection authenticationStatus = null) { RavenServer.AuthenticateConnection feature; if (authenticationStatus != null) { feature = authenticationStatus; } else { feature = HttpContext.Features.Get <IHttpAuthenticationFeature>() as RavenServer.AuthenticateConnection; } var status = feature?.Status; switch (status) { case null: case RavenServer.AuthenticationStatus.None: case RavenServer.AuthenticationStatus.NoCertificateProvided: case RavenServer.AuthenticationStatus.UnfamiliarCertificate: case RavenServer.AuthenticationStatus.UnfamiliarIssuer: case RavenServer.AuthenticationStatus.Expired: case RavenServer.AuthenticationStatus.NotYetValid: if (Server.Configuration.Security.AuthenticationEnabled == false) { return(null); } return((_, __) => false); // deny everything case RavenServer.AuthenticationStatus.Operator: case RavenServer.AuthenticationStatus.ClusterAdmin: return(null); case RavenServer.AuthenticationStatus.Allowed: return((database, requireWrite) => { switch (database) { case null: return false; case "*": return true; default: return feature.CanAccess(database, requireAdmin: false, requireWrite: requireWrite); } }); default: ThrowInvalidFeatureStatus(status.Value); return(null); // never hit } }
public async Task RemoteWatch() { var thumbprint = GetStringQueryString("thumbprint", required: false); CertificateDefinition clientConnectedCertificate = null; var canAccessDatabase = GetDatabaseAccessValidationFunc(); var currentCertificate = GetCurrentCertificate(); if (string.IsNullOrEmpty(thumbprint) == false && currentCertificate != null && thumbprint != currentCertificate.Thumbprint) { using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx)) { using (ctx.OpenReadTransaction()) { var certByThumbprint = ServerStore.Cluster.GetCertificateByThumbprint(ctx, thumbprint) ?? ServerStore.Cluster.GetLocalStateByThumbprint(ctx, thumbprint); if (certByThumbprint != null) { clientConnectedCertificate = JsonDeserializationServer.CertificateDefinition(certByThumbprint); } } if (clientConnectedCertificate != null) { // we're already connected as ClusterAdmin, here we're just limiting the access to databases based on the thumbprint of the originally connected certificated // so we'll send notifications only about relevant databases var authenticationStatus = new RavenServer.AuthenticateConnection(); authenticationStatus.SetBasedOnCertificateDefinition(clientConnectedCertificate); canAccessDatabase = GetDatabaseAccessValidationFunc(authenticationStatus); } } } using (var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync()) { try { await SendNotifications(canAccessDatabase, webSocket); } catch (OperationCanceledException) { // ignored } catch (Exception ex) { await HandleException(ex, webSocket); } } }
public static IEnumerable <RouteInformation> GetAuthorizedRoutes(RavenServer.AuthenticateConnection authenticateConnection, string db = null) { return(Routes.Where(route => { bool authorized = false; switch (authenticateConnection.Status) { case RavenServer.AuthenticationStatus.ClusterAdmin: authorized = true; break; case RavenServer.AuthenticationStatus.Operator: if (route.AuthorizationStatus != AuthorizationStatus.ClusterAdmin) { authorized = true; } break; case RavenServer.AuthenticationStatus.Allowed: if (route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin || route.AuthorizationStatus == AuthorizationStatus.Operator) { break; } if (route.TypeOfRoute == RouteInformation.RouteType.Databases && (db == null || authenticateConnection.CanAccess(db, route.AuthorizationStatus == AuthorizationStatus.DatabaseAdmin) == false)) { break; } authorized = true; break; default: if (route.AuthorizationStatus == AuthorizationStatus.UnauthenticatedClients) { authorized = true; } break; } return authorized; })); }
public void UnlikelyFailAuthorization(HttpContext context, string database, RavenServer.AuthenticateConnection feature, AuthorizationStatus authorizationStatus) { string message; if (feature == null || feature.Status == RavenServer.AuthenticationStatus.None || feature.Status == RavenServer.AuthenticationStatus.NoCertificateProvided) { message = "This server requires client certificate for authentication, but none was provided by the client."; } else { var name = feature.Certificate.FriendlyName; if (string.IsNullOrWhiteSpace(name)) { name = feature.Certificate.Subject; } if (string.IsNullOrWhiteSpace(name)) { name = feature.Certificate.ToString(false); } name += "(Thumbprint: " + feature.Certificate.Thumbprint + ")"; if (feature.Status == RavenServer.AuthenticationStatus.UnfamiliarCertificate) { message = "The supplied client certificate '" + name + "' is unknown to the server. In order to register your certificate please contact your system administrator."; } else if (feature.Status == RavenServer.AuthenticationStatus.Allowed) { message = "Could not authorize access to " + (database ?? "the server") + " using provided client certificate '" + name + "'."; } else if (feature.Status == RavenServer.AuthenticationStatus.Operator) { message = "Insufficient security clearance to access " + (database ?? "the server") + " using provided client certificate '" + name + "'."; } else if (feature.Status == RavenServer.AuthenticationStatus.Expired) { message = "The supplied client certificate '" + name + "' has expired on " + feature.Certificate.NotAfter.ToString("D") + ". Please contact your system administrator in order to obtain a new one."; } else if (feature.Status == RavenServer.AuthenticationStatus.NotYetValid) { message = "The supplied client certificate '" + name + "'cannot be used before " + feature.Certificate.NotBefore.ToString("D"); } else { message = "Access to the server was denied."; } } switch (authorizationStatus) { case AuthorizationStatus.ClusterAdmin: message += " ClusterAdmin access is required but not given to this certificate"; break; case AuthorizationStatus.Operator: message += " Operator/ClusterAdmin access is required but not given to this certificate"; break; case AuthorizationStatus.DatabaseAdmin: message += " DatabaseAdmin access is required but not given to this certificate"; break; } context.Response.StatusCode = (int)HttpStatusCode.Forbidden; using (var ctx = JsonOperationContext.ShortTermSingleUse()) using (var writer = new BlittableJsonTextWriter(ctx, context.Response.Body)) { DrainRequest(ctx, context); if (RavenServerStartup.IsHtmlAcceptable(context)) { context.Response.StatusCode = (int)HttpStatusCode.Redirect; context.Response.Headers["Location"] = "/auth-error.html?err=" + Uri.EscapeDataString(message); return; } ctx.Write(writer, new DynamicJsonValue { ["Type"] = "InvalidAuth", ["Message"] = message }); } }
public static void UnlikelyFailAuthorization(HttpContext context, string database, RavenServer.AuthenticateConnection feature, AuthorizationStatus authorizationStatus) { string message; if (feature == null || feature.Status == RavenServer.AuthenticationStatus.None || feature.Status == RavenServer.AuthenticationStatus.NoCertificateProvided) { message = "This server requires client certificate for authentication, but none was provided by the client."; } else { var name = feature.Certificate.FriendlyName; if (string.IsNullOrWhiteSpace(name)) { name = feature.Certificate.Subject; } if (string.IsNullOrWhiteSpace(name)) { name = feature.Certificate.ToString(false); } name += $"(Thumbprint: {feature.Certificate.Thumbprint})"; if (feature.Status == RavenServer.AuthenticationStatus.UnfamiliarCertificate) { message = $"The supplied client certificate '{name}' is unknown to the server. In order to register your certificate please contact your system administrator."; } else if (feature.Status == RavenServer.AuthenticationStatus.UnfamiliarIssuer) { message = $"The supplied client certificate '{name}' is unknown to the server but has a known Public Key Pinning Hash. Will not use it to authenticate because the issuer is unknown. To fix this, the admin can register the pinning hash of the *issuer* certificate: '{feature.IssuerHash}' in the '{RavenConfiguration.GetKey(x => x.Security.WellKnownIssuerHashes)}' configuration entry."; } else if (feature.Status == RavenServer.AuthenticationStatus.Allowed) { message = $"Could not authorize access to {(database ?? "the server")} using provided client certificate '{name}'."; } else if (feature.Status == RavenServer.AuthenticationStatus.Operator) { message = $"Insufficient security clearance to access {(database ?? "the server")} using provided client certificate '{name}'."; } else if (feature.Status == RavenServer.AuthenticationStatus.Expired) { message = $"The supplied client certificate '{name}' has expired on {feature.Certificate.NotAfter:D}. Please contact your system administrator in order to obtain a new one."; } else if (feature.Status == RavenServer.AuthenticationStatus.NotYetValid) { message = $"The supplied client certificate '{name}'cannot be used before {feature.Certificate.NotBefore:D}"; } else { message = "Access to the server was denied."; } } switch (authorizationStatus) { case AuthorizationStatus.ClusterAdmin: message += " ClusterAdmin access is required but not given to this certificate"; break; case AuthorizationStatus.Operator: message += " Operator/ClusterAdmin access is required but not given to this certificate"; break; case AuthorizationStatus.DatabaseAdmin: message += " DatabaseAdmin access is required but not given to this certificate"; break; } context.Response.StatusCode = (int)HttpStatusCode.Forbidden; using (var ctx = JsonOperationContext.ShortTermSingleUse()) using (var writer = new BlittableJsonTextWriter(ctx, context.Response.Body)) { DrainRequest(ctx, context); if (RavenServerStartup.IsHtmlAcceptable(context)) { context.Response.StatusCode = (int)HttpStatusCode.Redirect; context.Response.Headers["Location"] = "/auth-error.html?err=" + Uri.EscapeDataString(message); return; } ctx.Write(writer, new DynamicJsonValue { ["Type"] = "InvalidAuth", ["Message"] = message }); } }
internal bool CanAccessRoute(RouteInformation route, HttpContext context, string databaseName, RavenServer.AuthenticateConnection feature, out RavenServer.AuthenticationStatus authenticationStatus) { authenticationStatus = feature?.Status ?? RavenServer.AuthenticationStatus.None; switch (route.AuthorizationStatus) { case AuthorizationStatus.UnauthenticatedClients: var userWantsToAccessStudioMainPage = context.Request.Path == "/studio/index.html"; if (userWantsToAccessStudioMainPage) { switch (authenticationStatus) { case RavenServer.AuthenticationStatus.NoCertificateProvided: case RavenServer.AuthenticationStatus.Expired: case RavenServer.AuthenticationStatus.NotYetValid: case RavenServer.AuthenticationStatus.None: case RavenServer.AuthenticationStatus.UnfamiliarCertificate: case RavenServer.AuthenticationStatus.UnfamiliarIssuer: return(false); } } return(true); case AuthorizationStatus.ClusterAdmin: case AuthorizationStatus.Operator: case AuthorizationStatus.ValidUser: case AuthorizationStatus.DatabaseAdmin: case AuthorizationStatus.RestrictedAccess: switch (authenticationStatus) { case RavenServer.AuthenticationStatus.NoCertificateProvided: case RavenServer.AuthenticationStatus.Expired: case RavenServer.AuthenticationStatus.NotYetValid: case RavenServer.AuthenticationStatus.None: return(false); case RavenServer.AuthenticationStatus.UnfamiliarCertificate: case RavenServer.AuthenticationStatus.UnfamiliarIssuer: // we allow an access to the restricted endpoints with an unfamiliar certificate, since we will authorize it at the endpoint level if (route.AuthorizationStatus == AuthorizationStatus.RestrictedAccess) { return(true); } ; goto case RavenServer.AuthenticationStatus.None; case RavenServer.AuthenticationStatus.Allowed: if (route.AuthorizationStatus == AuthorizationStatus.Operator || route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin) { goto case RavenServer.AuthenticationStatus.None; } if (databaseName == null) { return(true); } if (feature.CanAccess(databaseName, route.AuthorizationStatus == AuthorizationStatus.DatabaseAdmin, route.EndpointType == EndpointType.Write)) { return(true); } goto case RavenServer.AuthenticationStatus.None; case RavenServer.AuthenticationStatus.Operator: if (route.AuthorizationStatus == AuthorizationStatus.ClusterAdmin) { goto case RavenServer.AuthenticationStatus.None; } return(true); case RavenServer.AuthenticationStatus.ClusterAdmin: return(true); default: throw new ArgumentOutOfRangeException(); } default: ThrowUnknownAuthStatus(route); return(false); // never hit } }
public void UnlikelyFailAuthorization(HttpContext context, string database, RavenServer.AuthenticateConnection feature) { string message; if (feature == null || feature.Status == RavenServer.AuthenticationStatus.None || feature.Status == RavenServer.AuthenticationStatus.NoCertificateProvided) { message = "This server requires client certificate for authentication, but none was provided by the client"; } else { var name = feature.Certificate.FriendlyName; if (string.IsNullOrWhiteSpace(name)) { name = feature.Certificate.Subject; } if (string.IsNullOrWhiteSpace(name)) { name = feature.Certificate.ToString(false); } if (feature.Status == RavenServer.AuthenticationStatus.UnfamiliarCertificate) { message = "The provided client certificate '" + name + "' is not on the allowed list of certificates that can access this server"; } else if (feature.Status == RavenServer.AuthenticationStatus.Allowed) { message = "The provided client certificate '" + name + "' is not authorized to access " + (database ?? "the server"); } else if (feature.Status == RavenServer.AuthenticationStatus.Operator) { message = "The provided client certificate '" + name + "' does not have sufficient level to access " + (database ?? "the server"); } else if (feature.Status == RavenServer.AuthenticationStatus.Expired) { message = "The provided client certificate '" + name + "' is expired on " + feature.Certificate.NotAfter; } else if (feature.Status == RavenServer.AuthenticationStatus.NotYetValid) { message = "The provided client certificate '" + name + "' is not yet valid because it starts on " + feature.Certificate.NotBefore; } else { message = "Access to this server was denied, but the reason why is confidential, you did not see this message and your memory will self destruct in 5 seconds."; } } context.Response.StatusCode = (int)HttpStatusCode.Forbidden; using (var ctx = JsonOperationContext.ShortTermSingleUse()) using (var writer = new BlittableJsonTextWriter(ctx, context.Response.Body)) { DrainRequest(ctx, context); if (RavenServerStartup.IsHtmlAcceptable(context)) { context.Response.StatusCode = (int)HttpStatusCode.Redirect; context.Response.Headers["Location"] = "/studio/auth-error.html?err=" + Uri.EscapeDataString(message); return; } ctx.Write(writer, new DynamicJsonValue { ["Type"] = "InvalidAuth", ["Message"] = message }); } }