Skip to content
Merged
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ Errors are highlighted, so you can quickly spot suspicious behavior or bugs in y

Take the investigation further with quick links into to your CloudTrail event history.

![Defend](design/readme/defend.png)

Generate or manually configure service control policies for your accounts to help you enforce least privilege access and meet compliance requirements.

Preview service control policies and apply them with the click of a button.

## Open Source or SaaS

Cloud Snitch is open sourced under the MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) so if you're feeling adventurous, you can deploy it directly to your own cloud. Alternatively, you can get up and running in under 5 minutes with an individual or team plan at [cloudsnitch.io](https://cloudsnitch.io).
Expand Down
Binary file modified design/assets/gallery.pxd
Binary file not shown.
Binary file added design/readme/defend.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added design/screenshots/defend.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/defend.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 24 additions & 3 deletions frontend/src/app/(public-area)/features/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const metadata: Metadata = {
const Page = () => {
return (
<div className="flex flex-col gap-4 [&_p]:my-4">
<div className="translucent-snow p-4 rounded-lg lg:grid lg:grid-flow-col lg:auto-cols-fr">
<div className="translucent-snow p-4 rounded-lg md:grid md:grid-flow-col md:auto-cols-fr lg:auto-cols-[3fr_2fr]">
<div className="pr-8">
<h1>
<span className="text-amethyst-gradient">Explore Activity</span> 🗺️
Expand All @@ -35,7 +35,7 @@ const Page = () => {
/>
</div>
</div>
<div className="translucent-snow p-4 rounded-lg lg:grid lg:grid-flow-col lg:auto-cols-fr">
<div className="translucent-snow p-4 rounded-lg md:grid md:grid-flow-col md:auto-cols-fr lg:auto-cols-[3fr_2fr]">
<div className="pr-8">
<h1>
<span className="text-light-coral-gradient">Collaborate With Teammates</span> 🤝
Expand All @@ -54,7 +54,7 @@ const Page = () => {
/>
</div>
</div>
<div className="translucent-snow p-4 rounded-lg lg:grid lg:grid-flow-col lg:auto-cols-fr">
<div className="translucent-snow p-4 rounded-lg md:grid md:grid-flow-col md:auto-cols-fr lg:auto-cols-[3fr_2fr]">
<div className="pr-8">
<h1>
<span className="text-amethyst-gradient">Expose Bugs and Suspicious Behavior</span> 👾
Expand All @@ -76,6 +76,27 @@ const Page = () => {
/>
</div>
</div>
<div className="translucent-snow p-4 rounded-lg md:grid md:grid-flow-col md:auto-cols-fr lg:auto-cols-[3fr_2fr]">
<div className="pr-8">
<h1>
<span className="text-light-coral-gradient">Defend in Depth</span> 🛡️
</h1>
<p>
Generate or manually configure service control policies for your accounts to help you enforce
least privilege access and meet compliance requirements.
</p>
<p>Preview service control policies and apply them with the click of a button.</p>
</div>
<div>
<Image
src="/images/defend.png"
alt="Expose Bugs and Suspicious Behavior"
width={1388}
height={800}
className="rounded-lg border-1 border-platinum"
/>
</div>
</div>
<div className="bg-english-violet text-snow p-4 rounded-lg">
<h1>Want more?</h1>
<p>
Expand Down
9 changes: 6 additions & 3 deletions frontend/src/app/(public-area)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ const Page = () => {
to the next level.
</p>
<p>
Cloud Snitch provides a sleek and intuitive way of exploring your AWS account activity.
Cloud Snitch provides a sleek and intuitive way of exploring your AWS account activity
along with controls to help you adopt best practices and meet compliance needs.
</p>
<p>
It&apos;s a great addition to any toolbox, regardless of if you&apos;re a hobbyist
that&apos;s just getting started with the cloud or a large enterprise with complex and
mature cloud infrastructure.
Expand All @@ -55,8 +58,8 @@ const Page = () => {
</Link>
</h2>
<p className="text-sm mt-4">
Cloud Snitch aims to do just one thing well: Enlighten you as to what&apos;s happening in your
cloud.
Cloud Snitch aims to do just one thing well: Make sure nothing happens in your cloud without
your knowledge.
</p>
<p className="text-sm mt-4">Check out our features to learn how we do it.</p>
<div className="grow" />
Expand Down
68 changes: 42 additions & 26 deletions frontend/src/app/(user-area)/teams/[teamId]/Rules.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Link from 'next/link';
import { Transition } from '@headlessui/react';

import { awsServices } from '@/aws';
import { Button, ChipEditor, Dialog, ErrorMessage, SuccessMessage, SyntaxHighlighter } from '@/components';
import { Button, ChipEditor, Dialog, ErrorMessage, InfoMessage, SuccessMessage, SyntaxHighlighter } from '@/components';
import { AWSAccount } from '@/generated/api';
import { useAwsRegions, useCurrentTeamId, useManagedAwsScp, useTeamAwsAccountsMap } from '@/hooks';
import { RuleSet } from '@/rules';
Expand Down Expand Up @@ -72,9 +72,13 @@ interface AccountPageProps {
onBack: () => void;
}

interface Message {
type: 'error' | 'info' | 'success';
message: string;
}

const AccountPage = ({ account, onBack }: AccountPageProps) => {
const [errorMessage, setErrorMessage] = useState('');
const [successMessage, setSuccessMessage] = useState('');
const [message, setMessage] = useState<Message | null>(null);
const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
const [isPreviewing, setIsPreviewing] = useState(false);
const dispatch = useDispatch();
Expand Down Expand Up @@ -108,8 +112,7 @@ const AccountPage = ({ account, onBack }: AccountPageProps) => {
return;
}
setIsLoadingSuggestions(true);
setErrorMessage('');
setSuccessMessage('');
setMessage(null);

try {
const [reports, awsAccessReport] = await Promise.all([
Expand Down Expand Up @@ -138,26 +141,29 @@ const AccountPage = ({ account, onBack }: AccountPageProps) => {
}

setRuleSet(ruleSet);
setSuccessMessage(
'Based on your recent account activity, we recommend the following rules. Please review them carefully and add or remove items as needed.',
setMessage(
scpRuleSet && ruleSet.equal(scpRuleSet)
? {
type: 'success',
message: 'Your rules are already up to date.',
}
: {
type: 'info',
message:
'Based on your recent account activity, the following rules are recommended. Please review them carefully and add or remove items as needed.',
},
);
} catch (err) {
setErrorMessage(err instanceof Error ? err.message : 'An unknown error occurred.');
setMessage({
type: 'error',
message: err instanceof Error ? err.message : 'An unknown error occurred.',
});
} finally {
setIsLoadingSuggestions(false);
}
};
impl();
}, [
isLoadingSuggestions,
setIsLoadingSuggestions,
dispatch,
teamId,
account,
setErrorMessage,
setSuccessMessage,
setRuleSet,
]);
}, [isLoadingSuggestions, setIsLoadingSuggestions, dispatch, teamId, account, setMessage, setRuleSet, scpRuleSet]);

return (
<div className="flex flex-col gap-4">
Expand All @@ -179,8 +185,9 @@ const AccountPage = ({ account, onBack }: AccountPageProps) => {
</div>
</div>

{errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>}
{successMessage && <SuccessMessage>{successMessage}</SuccessMessage>}
{message?.type === 'error' && <ErrorMessage>{message.message}</ErrorMessage>}
{message?.type === 'info' && <InfoMessage>{message.message}</InfoMessage>}
{message?.type === 'success' && <SuccessMessage>{message.message}</SuccessMessage>}

<div className="border border-english-violet/60 bg-white/20 text-sm rounded-lg p-2 flex flex-col gap-2">
<div>
Expand Down Expand Up @@ -253,7 +260,17 @@ const AccountPage = ({ account, onBack }: AccountPageProps) => {
</div>

<Dialog isOpen={isPreviewing} onClose={() => setIsPreviewing(false)} title="Policy Preview">
<PolicyPreview account={account} ruleSet={ruleSet} onSuccess={() => setIsPreviewing(false)} />
<PolicyPreview
account={account}
ruleSet={ruleSet}
onSuccess={() => {
setMessage({
type: 'success',
message: 'Policy applied successfully.',
});
setIsPreviewing(false);
}}
/>
</Dialog>
<Button disabled={!hasChanges} label="Preview Policy" onClick={() => setIsPreviewing(true)} />
</>
Expand Down Expand Up @@ -298,17 +315,16 @@ export const Rules = () => {
<Transition show={!account}>
<div className={pageClassName}>
<p>
You can use Cloud Snitch to enforce rules for the following AWS accounts. Cloud Snitch does this
by attaching{' '}
You can use Cloud Snitch to enforce rules for the following AWS accounts via{' '}
<Link
href="https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html"
className="external-link"
rel="noopener noreferrer"
target="_blank"
>
Service Control Policies
</Link>{' '}
to accounts.
service control policies
</Link>
.
</p>
<div className="flex flex-col max-h-[50vh] overflow-auto">
{sortedAccounts &&
Expand Down
15 changes: 12 additions & 3 deletions frontend/src/components/Dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Dialog as DialogImpl, DialogPanel, DialogTitle } from '@headlessui/react';
import { XMarkIcon } from '@heroicons/react/24/outline';
import { CloseButton, Dialog as DialogImpl, DialogBackdrop, DialogPanel, DialogTitle } from '@headlessui/react';
import React from 'react';

interface Props {
Expand All @@ -14,10 +15,18 @@ export const Dialog = (props: Props) => {

return (
<DialogImpl open={!!props.isOpen} onClose={() => props.onClose && props.onClose()} className="relative z-50">
<div className="fixed inset-0 flex w-screen items-center justify-center p-4 bg-radial from-black/10 to-black/40">
<DialogBackdrop
className="fixed inset-0 bg-radial from-black/10 to-black/40 ease-out duration-200 data-[closed]:opacity-0"
transition
/>
<div className="fixed inset-0 flex w-screen items-center justify-center p-4">
<DialogPanel
className={`min-w-lg ${width} space-y-4 translucent-snow border border-platinum rounded-xl p-8`}
className={`relative min-w-lg ${width} space-y-4 translucent-snow border border-platinum rounded-xl p-8 duration-200 ease-out data-[closed]:scale-95 data-[closed]:opacity-0`}
transition
>
<CloseButton className="absolute right-2 top-2 cursor-pointer opacity-20 hover:opacity-100 hover:text-majorelle-blue transition-opacity duration-200 ease-in-out">
<XMarkIcon className="h-[1.5rem]" />
</CloseButton>
{props.title && <DialogTitle className="font-bold">{props.title}</DialogTitle>}
{props.children}
</DialogPanel>
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/InfoMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { InformationCircleIcon } from '@heroicons/react/24/outline';

export const InfoMessage = ({ children }: { children: React.ReactNode }) => (
<p className="w-full rounded-md p-3 text-dark-purple bg-majorelle-blue/10 sm:text-sm sm:leading-6 [&_a]:underline">
<span className="inline-flex items-baseline">
<InformationCircleIcon className="h-6 w-6 shrink-0 self-center mr-2" />
<span>{children}</span>
</span>
</p>
);
1 change: 1 addition & 0 deletions frontend/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { DurationDropdown } from './DurationDropdown';
export { ErrorMessage } from './ErrorMessage';
export { type Filter, FilterDropdown } from './FilterDropdown';
export { GitHubIcon } from './GitHubIcon';
export { InfoMessage } from './InfoMessage';
export { Logo } from './Logo';
export { Markdown } from './Markdown';
export { MascotBox } from './MascotBox';
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,15 @@ export class RuleSet {
});
}

if (policy.Statement.length === 0) {
policy.Statement.push({
Sid: 'NoOp',
Effect: 'Allow',
Action: 'sts:GetCallerIdentity',
Resource: '*',
});
}

return policy;
}
}
Loading