Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions plugins/azure/databricks/workspacePrivateEndpoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
var async = require('async');
var helpers = require('../../../helpers/azure');

module.exports = {
title: 'Databricks Workspace Private Endpoints',
category: 'AI & ML',
domain: 'Machine Learning',
severity: 'Medium',
description: 'Ensures that Azure Databricks Workspace has an approved private endpoint connection.',
more_info: 'Private endpoints allow clients and services to access the Databricks workspace over an encrypted Private Link using a private IP address from the virtual network. This keeps traffic off the public internet and reduces the attack surface. A private endpoint connection only carries traffic once its connection state is approved.',
recommended_action: 'Create a private endpoint for the Databricks workspace and approve the private endpoint connection.',
link: 'https://learn.microsoft.com/en-us/azure/databricks/security/network/classic/private-link',
apis: ['databricks:listWorkspaces'],
realtime_triggers: ['microsoftdatabricks:workspaces:write','microsoftdatabricks:workspaces:delete'],

run: function(cache, settings, callback) {
const results = [];
const source = {};
const locations = helpers.locations(settings.govcloud);

async.each(locations.databricks, function(location, rcb) {
const databricks = helpers.addSource(cache, source,
['databricks', 'listWorkspaces', location]);

if (!databricks) return rcb();

if (databricks.err || !databricks.data) {
helpers.addResult(results, 3, 'Unable to query for Databricks Workspaces: ' + helpers.addError(databricks), location);
return rcb();
}

if (!databricks.data.length) {
helpers.addResult(results, 0, 'No existing Databricks Workspaces found', location);
return rcb();
}

for (let workspace of databricks.data) {
if (!workspace.id) continue;

var approved = workspace.privateEndpointConnections && workspace.privateEndpointConnections.length ?
workspace.privateEndpointConnections.some(connection => connection.properties &&
connection.properties.privateLinkServiceConnectionState &&
connection.properties.privateLinkServiceConnectionState.status &&
connection.properties.privateLinkServiceConnectionState.status.toLowerCase() === 'approved') : false;

if (approved) {
helpers.addResult(results, 0, 'Databricks workspace has an approved private endpoint connection', location, workspace.id);
} else {
helpers.addResult(results, 2, 'Databricks workspace does not have an approved private endpoint connection', location, workspace.id);
}
}

rcb();
}, function() {
// Global checking goes here
callback(null, results, source);
});
}
};
140 changes: 140 additions & 0 deletions plugins/azure/databricks/workspacePrivateEndpoints.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
var expect = require('chai').expect;
var workspacePrivateEndpoints = require('./workspacePrivateEndpoints.js');

const workspaces = [
{
"managedResourceGroupId": "/subscriptions/1234/resourceGroups/test",
"privateEndpointConnections": [],
"id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace",
"name": "test-workspace",
"type": "Microsoft.Databricks/workspaces",
"sku": {
"name": "premium"
},
"location": "eastus",
"tags": {}
},
{
"managedResourceGroupId": "/subscriptions/1234/resourceGroups/test",
"privateEndpointConnections": [
{
"id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace/privateEndpointConnections/test-connection",
"name": "test-connection",
"type": "Microsoft.Databricks/workspaces/privateEndpointConnections",
"properties": {
"privateLinkServiceConnectionState": {
"status": "Pending",
"actionsRequired": "None"
}
}
}
],
"id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace",
"name": "test-workspace",
"type": "Microsoft.Databricks/workspaces",
"sku": {
"name": "premium"
},
"location": "eastus",
"tags": {}
},
{
"managedResourceGroupId": "/subscriptions/1234/resourceGroups/test",
"privateEndpointConnections": [
{
"id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace/privateEndpointConnections/test-connection",
"name": "test-connection",
"type": "Microsoft.Databricks/workspaces/privateEndpointConnections",
"properties": {
"privateLinkServiceConnectionState": {
"status": "Approved",
"actionsRequired": "None"
}
}
}
],
"id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace",
"name": "test-workspace",
"type": "Microsoft.Databricks/workspaces",
"sku": {
"name": "premium"
},
"location": "eastus",
"tags": {}
},
];


const createCache = (workspaces, err) => {

return {
databricks: {
listWorkspaces: {
'eastus': {
data: workspaces,
err: err
}
}
}
};
};

describe('workspacePrivateEndpoints', function () {
describe('run', function () {

it('should give a passing result if no Databricks workspaces are found', function (done) {
const cache = createCache([], null);
workspacePrivateEndpoints.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
expect(results[0].message).to.include('No existing Databricks Workspaces found');
expect(results[0].region).to.equal('eastus');
done();
});
});

it('should give unknown result if unable to query for Databricks workspaces', function (done) {
const cache = createCache(null, ['error']);
workspacePrivateEndpoints.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(3);
expect(results[0].message).to.include('Unable to query for Databricks Workspaces');
expect(results[0].region).to.equal('eastus');
done();
});
});

it('should give passing result if Databricks workspace has an approved private endpoint connection', function (done) {
const cache = createCache([workspaces[2]], null);
workspacePrivateEndpoints.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
expect(results[0].message).to.include('Databricks workspace has an approved private endpoint connection');
expect(results[0].region).to.equal('eastus');
done();
});
});

it('should give failing result if Databricks workspace does not have private endpoint connections', function (done) {
const cache = createCache([workspaces[0]], null);
workspacePrivateEndpoints.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(2);
expect(results[0].message).to.include('Databricks workspace does not have an approved private endpoint connection');
expect(results[0].region).to.equal('eastus');
done();
});
});

it('should give failing result if Databricks workspace private endpoint connection is not approved', function (done) {
const cache = createCache([workspaces[1]], null);
workspacePrivateEndpoints.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(2);
expect(results[0].message).to.include('Databricks workspace does not have an approved private endpoint connection');
expect(results[0].region).to.equal('eastus');
done();
});
});
});
});
53 changes: 53 additions & 0 deletions plugins/azure/databricks/workspacePublicAccess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
var async = require('async');
var helpers = require('../../../helpers/azure');

module.exports = {
title: 'Databricks Workspace Public Access',
category: 'AI & ML',
domain: 'Machine Learning',
severity: 'Medium',
description: 'Ensures that Azure Databricks Workspace has public network access disabled.',
more_info: 'Disabling public network access ensures that the Databricks workspace is not reachable over the public internet and can only be accessed through private endpoints within trusted networks. This reduces the attack surface and the risk of unauthorized access.',
recommended_action: 'Modify Databricks workspace networking settings and set Allow Public Network Access to Disabled.',
link: 'https://learn.microsoft.com/en-us/azure/databricks/security/network/front-end/front-end-private-connect',
apis: ['databricks:listWorkspaces'],
realtime_triggers: ['microsoftdatabricks:workspaces:write','microsoftdatabricks:workspaces:delete'],

run: function(cache, settings, callback) {
const results = [];
const source = {};
const locations = helpers.locations(settings.govcloud);

async.each(locations.databricks, function(location, rcb) {
const databricks = helpers.addSource(cache, source,
['databricks', 'listWorkspaces', location]);

if (!databricks) return rcb();

if (databricks.err || !databricks.data) {
helpers.addResult(results, 3, 'Unable to query for Databricks Workspaces: ' + helpers.addError(databricks), location);
return rcb();
}

if (!databricks.data.length) {
helpers.addResult(results, 0, 'No existing Databricks Workspaces found', location);
return rcb();
}

for (let workspace of databricks.data) {
if (!workspace.id) continue;

if (workspace.publicNetworkAccess && workspace.publicNetworkAccess.toLowerCase() === 'disabled') {
helpers.addResult(results, 0, 'Databricks workspace has public network access disabled', location, workspace.id);
} else {
helpers.addResult(results, 2, 'Databricks workspace has public network access enabled', location, workspace.id);
}
}

rcb();
}, function() {
// Global checking goes here
callback(null, results, source);
});
}
};
93 changes: 93 additions & 0 deletions plugins/azure/databricks/workspacePublicAccess.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
var expect = require('chai').expect;
var workspacePublicAccess = require('./workspacePublicAccess.js');

const workspaces = [
{
"managedResourceGroupId": "/subscriptions/1234/resourceGroups/test",
"publicNetworkAccess": "Enabled",
"id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace",
"name": "test-workspace",
"type": "Microsoft.Databricks/workspaces",
"sku": {
"name": "premium"
},
"location": "eastus",
"tags": {}
},
{
"managedResourceGroupId": "/subscriptions/1234/resourceGroups/test",
"publicNetworkAccess": "Disabled",
"id": "/subscriptions/1234/resourceGroups/test/providers/Microsoft.Databricks/workspaces/test-workspace",
"name": "test-workspace",
"type": "Microsoft.Databricks/workspaces",
"sku": {
"name": "premium"
},
"location": "eastus",
"tags": {}
},
];


const createCache = (workspaces, err) => {

return {
databricks: {
listWorkspaces: {
'eastus': {
data: workspaces,
err: err
}
}
}
};
};

describe('workspacePublicAccess', function () {
describe('run', function () {

it('should give a passing result if no Databricks workspaces are found', function (done) {
const cache = createCache([], null);
workspacePublicAccess.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
expect(results[0].message).to.include('No existing Databricks Workspaces found');
expect(results[0].region).to.equal('eastus');
done();
});
});

it('should give unknown result if unable to query for Databricks workspaces', function (done) {
const cache = createCache(null, ['error']);
workspacePublicAccess.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(3);
expect(results[0].message).to.include('Unable to query for Databricks Workspaces');
expect(results[0].region).to.equal('eastus');
done();
});
});

it('should give passing result if Databricks workspace has public network access disabled', function (done) {
const cache = createCache([workspaces[1]], null);
workspacePublicAccess.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(0);
expect(results[0].message).to.include('Databricks workspace has public network access disabled');
expect(results[0].region).to.equal('eastus');
done();
});
});

it('should give failing result if Databricks workspace has public network access enabled', function (done) {
const cache = createCache([workspaces[0]], null);
workspacePublicAccess.run(cache, {}, (err, results) => {
expect(results.length).to.equal(1);
expect(results[0].status).to.equal(2);
expect(results[0].message).to.include('Databricks workspace has public network access enabled');
expect(results[0].region).to.equal('eastus');
done();
});
});
});
});
Loading
Loading