Skip to content
This repository was archived by the owner on Oct 16, 2025. It is now read-only.

Commit 264500c

Browse files
authored
Merge pull request #43 from GoogleCloudPlatform/gcs-s3-inspect
gcs-s3-dlp-inspect cloud build
2 parents 123f1b6 + 074c737 commit 264500c

12 files changed

Lines changed: 277 additions & 33 deletions

README.md

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -535,9 +535,9 @@ DLP Inspect template:
535535
536536
```
537537

538-
### DLP Inspection for Data Stored in AWS S3 Bucket
538+
### DLP Inspection for Data Stored in AWS S3 and GCS Bucket
539539

540-
This PoC can be used to inspect large scale dataset (csv, txt) stored in AWS S3 bucket. It uses Dataflow S3 connector, invoke DLP Inspect API to inspect data based on some configuration specified in a DLP inspect template. It stores the result in BQ.
540+
This PoC can be used to inspect large scale dataset (csv, txt) stored in AWS S3 and GCS buckets. It uses Dataflow S3 connector, invoke DLP Inspect API to inspect data based on some configuration specified in a DLP inspect template. It stores the result in BQ.
541541
### How it works?
542542
1. Build and Run the pipline in a GCP project using Dataflow. Please ensure you have enabled DLP and DF apis before executing.
543543

@@ -561,9 +561,79 @@ gradle build -DmainClass=com.google.swarm.tokenization.S3Import --x test
561561
562562
To Run:
563563
564-
gradle run -DmainClass=com.google.swarm.tokenization.S3Import -Pargs=" --streaming --project=<id> --runner=DataflowRunner --awsAccessKey=<key>--awsSecretKey=<key>--s3BucketUrl=s3://<bucket>/*.* --inspectTemplateName=projects/<id>/inspectTemplates/<template_id> --awsRegion=ca-central-1 --numWorkers=50 --workerMachineType=n1-highmem-16 --maxNumWorkers=50 --autoscalingAlgorithm=NONE --enableStreamingEngine --tempLocation=gs://<bucket>/temp --dataSetId=<dataset_id> --s3ThreadPoolSize=1000 --maxConnections=1000000 --socketTimeout=100 --connectionTimeout=100"
564+
gradle run -DmainClass=com.google.swarm.tokenization.S3Import -Pargs=" --streaming --project=<id> --runner=DataflowRunner --awsAccessKey=<key>--awsSecretKey=<key>--s3BucketUrl=s3://<bucket>/*.* --gcsBucketUrl=gs://<bucket>/*.* --inspectTemplateName=projects/<id>/inspectTemplates/<template_id> --awsRegion=ca-central-1 --numWorkers=50 --workerMachineType=n1-highmem-16 --maxNumWorkers=50 --autoscalingAlgorithm=NONE --enableStreamingEngine --tempLocation=gs://<bucket>/temp --dataSetId=<dataset_id> --s3ThreadPoolSize=1000 --maxConnections=1000000 --socketTimeout=100 --connectionTimeout=100"
565565
566566
```
567+
568+
### Run Using Cloud Build
569+
1. Modify inspect config file (gcs-s3-inspect-config.json) to add/update the info types you would lilke to use for scan. Below snippet is a sample config file used for demo.
570+
571+
```
572+
{
573+
"inspectTemplate": {
574+
"displayName": "DLP Inspection Config For Demo",
575+
"description": "DLP Config To Inspect GCS and S3 Bucket",
576+
"inspectConfig": {
577+
"infoTypes": [
578+
{
579+
"name": "EMAIL_ADDRESS"
580+
},
581+
{
582+
"name": "CREDIT_CARD_NUMBER"
583+
},
584+
{
585+
"name": "PHONE_NUMBER"
586+
},
587+
{
588+
"name": "US_SOCIAL_SECURITY_NUMBER"
589+
},
590+
{
591+
"name": "IP_ADDRESS"
592+
}
593+
],
594+
"minLikelihood": "POSSIBLE",
595+
"customInfoTypes": [
596+
{
597+
"infoType": {
598+
"name": "ONLINE_USER_ID"
599+
},
600+
"regex": {
601+
"pattern": "\\b:\\d{16}"
602+
}
603+
}
604+
]
605+
}
606+
},
607+
}
608+
609+
```
610+
611+
2. Export Required Parameters
612+
613+
```
614+
export AWS_ACCESS_KEY=<aws_access_key>
615+
export AWS_SECRET_KEY=<aws_secret_key>
616+
export S3_BUCKET_URL=<s3_bucket_url>
617+
export GCS_BUCKET_URL=<gcs_bucket_url>
618+
export AWS_REGION=<aws_region>
619+
export BQ_DATASET=<bq_dataset>
620+
621+
```
622+
623+
3. Run Cloud Build command
624+
625+
```
626+
gcloud builds submit . --config dlp-demo-s3-gcs-inspect.yaml \
627+
--substitutions _AWS_ACCESS_KEY=$AWS_ACCESS_KEY, \
628+
_API_KEY=$(gcloud auth print-access-token), \
629+
_AWS_SECRET_KEY=$AWS_SECRET_KEY, \
630+
_S3_BUCKET_URL=$S3_BUCKET_URL, \
631+
_GCS_BUCKET_URL=$GCS_BUCKET_URL, \
632+
_AWS_REGION=$AWS_REGION, \
633+
_BQ_DATASET=$BQ_DATASET
634+
635+
```
636+
567637
### Testing Configuration
568638
This PoC was built to process large scale data by scaling number of workers horizontally. During our test run, we have successfully inspected 1.3 TB of data in less than 10 minutes. It's recommended to use n1-highmem-16 machines as it allows Dataflow to reserve more JVM heap memory.
569639

@@ -580,6 +650,9 @@ Below configurations are common for both setup:
580650

581651

582652
### Some Screenshots from the PoC run
653+
#### Dataflow Job
654+
![Dataflow_DAG](diagrams/s3_1.png)
655+
![Dataflow_DAG](diagrams/s3_2.png)
583656

584657
#### S3 Bucket
585658

build.gradle

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,17 @@ apply plugin: "com.diffplug.gradle.spotless"
4545

4646
mainClassName = System.getProperty("mainClass")
4747
//mainClassName = 'com.google.swarm.tokenization.tool.DLPTemplateHelper'
48-
48+
jib {
49+
from {
50+
image = 'gcr.io/dataflow-templates-base/java8-template-launcher-base:latest'
51+
}
52+
container {
53+
appRoot = '/template/df-dlp-inspect-template'
54+
entrypoint = 'INHERIT'
55+
environment = [DATAFLOW_JAVA_COMMAND_SPEC:'/template/df-dlp-inspect-template/resources/java_command_spec.json']
56+
57+
}
58+
}
4959
group 'com.google.swarm'
5060
version '0.1.0'
5161

create-df-template.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2019 Google Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.#!/usr/bin/env bash
15+
# Copyright 2019 Google Inc.
16+
#
17+
# Licensed under the Apache License, Version 2.0 (the "License");
18+
# you may not use this file except in compliance with the License.
19+
# You may obtain a copy of the License at
20+
#
21+
# http://www.apache.org/licenses/LICENSE-2.0
22+
#
23+
# Unless required by applicable law or agreed to in writing, software
24+
# distributed under the License is distributed on an "AS IS" BASIS,
25+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26+
# See the License for the specific language governing permissions and
27+
# limitations under the License.
28+
29+
set -x
30+
31+
PROJECT_ID=$1
32+
AWS_ACCESS_KEY=$2
33+
API_KEY=$3
34+
AWS_SECRET_KEY=$4
35+
S3_BUCKET_URL=$5
36+
GCS_BUCKET_URL=$6
37+
AWS_REGION=$7
38+
BQ_DATASET=$8
39+
INSPECT_CONFIG="@gcs-s3-inspect-config.json"
40+
INSPECT_TEMPLATE_OUTPUT="inspect-template.json"
41+
DLP_API_ROOT_URL="https://dlp.googleapis.com"
42+
INSPECT_TEMPLATE_API="${DLP_API_ROOT_URL}/v2/projects/${PROJECT_ID}/inspectTemplates"
43+
curl -X POST -H "Content-Type: application/json" \
44+
-H "Authorization: Bearer ${API_KEY}" \
45+
"${INSPECT_TEMPLATE_API}"`` \
46+
-d "${INSPECT_CONFIG}"\
47+
-o "${INSPECT_TEMPLATE_OUTPUT}"
48+
more ${INSPECT_TEMPLATE_OUTPUT}
49+
INSPECT_TEMPLATE_NAME=$(jq -c '.name' ${INSPECT_TEMPLATE_OUTPUT})
50+
# publicly hosted image
51+
DYNAMIC_TEMPLATE_BUCKET_SPEC=gs://dynamic-template/dynamic_template_dlp_inspect.json
52+
JOB_NAME="dlp-inspect-pipeline-`date +%Y%m%d-%H%M%S-%N`"
53+
echo $JOB_NAME
54+
GCS_STAGING_LOCATION=gs://dynamic-template/log
55+
TEMP_LOCATION=gs://dynamic-template/temp
56+
PARAMETERS_CONFIG='{
57+
"jobName":"'$JOB_NAME'",
58+
"parameters":{
59+
"streaming":"true",
60+
"enableStreamingEngine":"true",
61+
"autoscalingAlgorithm":"NONE",
62+
"workerMachineType": "n1-standard-8",
63+
"numWorkers":"50",
64+
"maxNumWorkers":"50",
65+
"awsAccessKey":"'$AWS_ACCESS_KEY'",
66+
"awsSecretKey":"'$AWS_SECRET_KEY'",
67+
"s3BucketUrl":"'$S3_BUCKET_URL'",
68+
"gcsBucketUrl":"'$GCS_BUCKET_URL'",
69+
"inspectTemplateName":'$INSPECT_TEMPLATE_NAME',
70+
"s3ThreadPoolSize":"1000",
71+
"maxConnections":"1000000",
72+
"socketTimeout":"100",
73+
"connectionTimeout":"100",
74+
"tempLocation":"'$TEMP_LOCATION'",
75+
"awsRegion":"'$AWS_REGION'",
76+
"dataSetId":"'$BQ_DATASET'",
77+
}
78+
}'
79+
DF_API_ROOT_URL="https://dataflow.googleapis.com"
80+
TEMPLATES_LAUNCH_API="${DF_API_ROOT_URL}/v1b3/projects/${PROJECT_ID}/templates:launch"
81+
curl -X POST -H "Content-Type: application/json" \
82+
-H "Authorization: Bearer ${API_KEY}" \
83+
"${TEMPLATES_LAUNCH_API}"`
84+
`"?validateOnly=false"`
85+
`"&dynamicTemplate.gcsPath=${DYNAMIC_TEMPLATE_BUCKET_SPEC}"` \
86+
`"&dynamicTemplate.stagingLocation=${GCS_STAGING_LOCATION}" \
87+
-d "${PARAMETERS_CONFIG}"

diagrams/s3_1.png

61 KB
Loading

diagrams/s3_2.png

76.9 KB
Loading

dlp-demo-s3-gcs-inspect.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
# Copyright 2019 Google Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
steps:
16+
- name: 'ubuntu'
17+
args: ['bash', '-c','apt-get -q update && apt-get install -qqy curl && apt-get install -qqy jq;sh create-df-template.sh ${PROJECT_ID} ${_AWS_ACCESS_KEY} ${_API_KEY} ${_AWS_SECRET_KEY} ${_S3_BUCKET_URL} ${_GCS_BUCKET_URL} ${_AWS_REGION} ${_BQ_DATASET}']

gcs-s3-inspect-config.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"inspectTemplate": {
3+
"displayName": "DLP Inspection Config For Demo",
4+
"description": "DLP Config To Inspect GCS and S3 Bucket",
5+
"inspectConfig": {
6+
"infoTypes": [
7+
{
8+
"name": "EMAIL_ADDRESS"
9+
},
10+
{
11+
"name": "CREDIT_CARD_NUMBER"
12+
},
13+
{
14+
"name": "PHONE_NUMBER"
15+
},
16+
{
17+
"name": "US_SOCIAL_SECURITY_NUMBER"
18+
},
19+
{
20+
"name": "IP_ADDRESS"
21+
}
22+
],
23+
"minLikelihood": "POSSIBLE",
24+
"customInfoTypes": [
25+
{
26+
"infoType": {
27+
"name": "ONLINE_USER_ID"
28+
},
29+
"regex": {
30+
"pattern": "\\b:\\d{16}"
31+
}
32+
}
33+
]
34+
}
35+
},
36+
}

src/main/java/com/google/swarm/tokenization/S3Import.java

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
import org.apache.beam.sdk.metrics.Counter;
5252
import org.apache.beam.sdk.metrics.Metrics;
5353
import org.apache.beam.sdk.options.PipelineOptionsFactory;
54-
import org.apache.beam.sdk.options.ValueProvider;
5554
import org.apache.beam.sdk.transforms.DoFn;
5655
import org.apache.beam.sdk.transforms.Flatten;
5756
import org.apache.beam.sdk.transforms.ParDo;
@@ -116,13 +115,28 @@ public static void main(String[] args) {
116115
WithKeys.of(file -> file.getMetadata().resourceId().getFilename().toString()))
117116
.setCoder(KvCoder.of(StringUtf8Coder.of(), ReadableFileCoder.of()));
118117

118+
// gcs files
119+
PCollection<KV<String, ReadableFile>> gcsFiles =
120+
p.apply(
121+
"Poll GCS Files",
122+
FileIO.match()
123+
.filepattern(options.getGcsBucketUrl())
124+
.continuously(DEFAULT_POLL_INTERVAL, Watch.Growth.never()))
125+
.apply("GCS File Match", FileIO.readMatches().withCompression(Compression.AUTO))
126+
.apply(
127+
"Add GCS File Name as Key",
128+
WithKeys.of(file -> file.getMetadata().resourceId().getFilename().toString()))
129+
.setCoder(KvCoder.of(StringUtf8Coder.of(), ReadableFileCoder.of()));
130+
119131
PCollection<KV<String, ReadableFile>> files =
120-
s3Files.apply(
121-
"Fixed Window",
122-
Window.<KV<String, ReadableFile>>into(FixedWindows.of(WINDOW_INTERVAL))
123-
.triggering(AfterWatermark.pastEndOfWindow())
124-
.discardingFiredPanes()
125-
.withAllowedLateness(Duration.ZERO));
132+
PCollectionList.of(ImmutableList.of(gcsFiles, s3Files))
133+
.apply("File List", Flatten.pCollections())
134+
.apply(
135+
"Fixed Window",
136+
Window.<KV<String, ReadableFile>>into(FixedWindows.of(WINDOW_INTERVAL))
137+
.triggering(AfterWatermark.pastEndOfWindow())
138+
.discardingFiredPanes()
139+
.withAllowedLateness(Duration.ZERO));
126140

127141
PCollectionTuple contents =
128142
files.apply(
@@ -245,12 +259,12 @@ public OffsetRangeTracker newTracker(OffsetRange range) {
245259
@SuppressWarnings("serial")
246260
public static class TokenizeData extends DoFn<KV<String, String>, KV<String, TableRow>> {
247261
private String projectId;
248-
private ValueProvider<String> inspectTemplateName;
262+
private String inspectTemplateName;
249263
private Builder requestBuilder;
250264
private final Counter numberOfBytesInspected =
251265
Metrics.counter(TokenizeData.class, "NumberOfBytesInspected");
252266

253-
public TokenizeData(String projectId, ValueProvider<String> inspectTemplateName) {
267+
public TokenizeData(String projectId, String inspectTemplateName) {
254268
this.projectId = projectId;
255269
this.inspectTemplateName = inspectTemplateName;
256270
}
@@ -260,7 +274,7 @@ public void setup() {
260274
this.requestBuilder =
261275
InspectContentRequest.newBuilder()
262276
.setParent(ProjectName.of(this.projectId).toString())
263-
.setInspectTemplateName(this.inspectTemplateName.get());
277+
.setInspectTemplateName(this.inspectTemplateName);
264278
}
265279

266280
@ProcessElement
@@ -338,18 +352,18 @@ public static class BQDestination
338352
extends DynamicDestinations<KV<String, TableRow>, KV<String, TableRow>> {
339353

340354
private static final long serialVersionUID = 1L;
341-
private ValueProvider<String> datasetName;
355+
private String datasetName;
342356
private String projectId;
343357

344-
public BQDestination(ValueProvider<String> datasetName, String projectId) {
358+
public BQDestination(String datasetName, String projectId) {
345359
this.datasetName = datasetName;
346360
this.projectId = projectId;
347361
}
348362

349363
@Override
350364
public KV<String, TableRow> getDestination(ValueInSingleWindow<KV<String, TableRow>> element) {
351365
String key = element.getValue().getKey();
352-
String tableName = String.format("%s:%s.%s", projectId, datasetName.get(), key);
366+
String tableName = String.format("%s:%s.%s", projectId, datasetName, key);
353367
LOG.debug("Table Name {}", tableName);
354368
return KV.of(tableName, element.getValue().getValue());
355369
}

src/main/java/com/google/swarm/tokenization/common/AWSOptionParser.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public static void formatOptions(S3ImportOptions options) {
3434

3535
options.setClientConfiguration(configuration);
3636

37-
if (options.getS3BucketUrl().get().toLowerCase().startsWith(AWS_S3_PREFIX)) {
37+
if (options.getS3BucketUrl().toLowerCase().startsWith(AWS_S3_PREFIX)) {
3838
setAwsCredentials(options);
3939
}
4040

@@ -46,8 +46,7 @@ public static void formatOptions(S3ImportOptions options) {
4646
private static void setAwsCredentials(S3ImportOptions options) {
4747
options.setAwsCredentialsProvider(
4848
new AWSStaticCredentialsProvider(
49-
new BasicAWSCredentials(
50-
options.getAwsAccessKey().get(), options.getAwsSecretKey().get())));
49+
new BasicAWSCredentials(options.getAwsAccessKey(), options.getAwsSecretKey())));
5150
}
5251

5352
private static void setAwsDefaultRegion(S3ImportOptions options) {

0 commit comments

Comments
 (0)