1-
2- --
3- -- s3_get
1+ --
2+ -- s3_request
43--
54-- Installation:
65--
76-- CREATE EXTENSION pgcrypto;
87-- CREATE EXTENSION http;
98--
109-- Utility function to take S3 object and access keys and create
11- -- a signed HTTP GET request using the AWS4 signing scheme.
10+ -- a signed HTTP request using the AWS4 signing scheme.
1211-- https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
1312--
1413-- Various pieces of the request are gathered into strings bundled together
1817--
1918-- https://cleverelephant-west-1.s3.amazonaws.com/META.json
2019--
21- -- SELECT * FROM s3_get(
20+ -- SELECT * FROM s3_request(
21+ -- 'your_s3_access_key', -- access
22+ -- 'your_s3_secret_key', -- secret
23+ -- 'us-west-1', -- region
24+ -- 'cleverelephant-west-1', -- bucket
25+ -- 'META.json', -- object name
26+ -- );
27+ --
28+ --
29+ -- Created and delete objects too!
30+ --
31+ -- SELECT * FROM s3_request(
32+ -- 'your_s3_access_key', -- access
33+ -- 'your_s3_secret_key', -- secret
34+ -- 'us-west-1', -- region
35+ -- 'cleverelephant-west-1', -- bucket
36+ -- 'testfile.txt', -- object name
37+ -- 'PUT', -- http method
38+ -- 'this is a test' -- payload
39+ -- 'text/plain' -- payload mime type
40+ -- );
41+ --
42+ -- SELECT * FROM s3_request(
43+ -- 'your_s3_access_key', -- access
44+ -- 'your_s3_secret_key', -- secret
45+ -- 'us-west-1', -- region
46+ -- 'cleverelephant-west-1', -- bucket
47+ -- 'testfile.txt', -- object name
48+ -- );
49+ --
50+ -- SELECT * FROM s3_request(
2251-- 'your_s3_access_key', -- access
2352-- 'your_s3_secret_key', -- secret
2453-- 'us-west-1', -- region
2554-- 'cleverelephant-west-1', -- bucket
26- -- 'META.json' -- object
55+ -- 'testfile.txt', -- object name
56+ -- 'DELETE' -- http method
2757-- );
2858--
29- CREATE OR REPLACE FUNCTION s3_get (
59+
60+ CREATE OR REPLACE FUNCTION s3_request (
3061 access_key TEXT ,
3162 secret_key TEXT ,
3263 region TEXT ,
3364 bucket TEXT ,
34- object_key TEXT
65+ object_key TEXT ,
66+ http_method TEXT DEFAULT ' GET' ,
67+ object_payload TEXT DEFAULT NULL ,
68+ object_mimetype TEXT DEFAULT ' text/plain'
3569) RETURNS http_response AS $$
3670DECLARE
37- http_method TEXT := ' GET' ;
3871 host TEXT := bucket || ' .s3.' || region || ' .amazonaws.com' ;
3972 endpoint TEXT := ' https://' || host || ' /' || object_key;
4073 canonical_uri TEXT := ' /' || object_key;
@@ -45,7 +78,9 @@ DECLARE
4578 now TIMESTAMP := now() AT TIME ZONE ' UTC' ;
4679 amz_date TEXT := to_char(now, ' YYYYMMDD"T"HH24MISS"Z"' );
4780 date_stamp TEXT := to_char(now, ' YYYYMMDD' );
48- empty_payload_hash TEXT := ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' ;
81+
82+ -- Must use this magic hash if the payload is empty
83+ payload_hash TEXT := ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' ;
4984
5085 canonical_headers TEXT ;
5186 canonical_request TEXT ;
@@ -62,18 +97,33 @@ DECLARE
6297 request http_request;
6398BEGIN
6499
100+ http_method := upper (http_method);
101+
102+ IF object_payload IS NOT NULL
103+ THEN
104+ payload_hash := encode(digest(convert_to(object_payload, ' UTF8' ), ' sha256' ), ' hex' );
105+ END IF;
106+
65107 -- Construct the canonical headers
66108 canonical_headers := ' host:' || host || E' \n '
67- || ' x-amz-content-sha256:' || empty_payload_hash || E' \n '
109+ || ' x-amz-content-sha256:' || payload_hash || E' \n '
68110 || ' x-amz-date:' || amz_date || E' \n ' ;
69111
112+ -- Signed headers must be in alphabetical order
113+ -- so content-type goes first
114+ IF object_payload IS NOT NULL
115+ THEN
116+ canonical_headers := ' content-type:' || lower (object_mimetype) || E' \n ' || canonical_headers;
117+ signed_headers := ' content-type;' || signed_headers;
118+ END IF;
119+
70120 -- Create the canonical request
71121 canonical_request := http_method || E' \n ' ||
72122 canonical_uri || E' \n ' ||
73123 canonical_querystring || E' \n ' ||
74124 canonical_headers || E' \n ' ||
75125 signed_headers || E' \n ' ||
76- empty_payload_hash ;
126+ payload_hash ;
77127
78128 -- Define the credential scope
79129 credential_scope := date_stamp || ' /' || region || ' /' || service || ' /aws4_request' ;
@@ -108,29 +158,27 @@ BEGIN
108158
109159 -- Perform the HTTP request
110160 request := (
111- ' GET ' ,
112- endpoint,
113- http_headers(' Authorization' , authorization_header,
114- ' x-amz-content-sha256' , empty_payload_hash ,
115- ' x-amz-date' , amz_date,
116- ' host' , host),
117- NULL ,
118- NULL
161+ http_method ,
162+ endpoint,
163+ http_headers(' Authorization' , authorization_header,
164+ ' x-amz-content-sha256' , payload_hash ,
165+ ' x-amz-date' , amz_date,
166+ ' host' , host),
167+ object_mimetype ,
168+ object_payload
119169 )::http_request;
120170
121171 -- Getting the canonical request and payload strings perfectly
122172 -- formatted is an important step so debugging here in case
123173 -- S3 rejects signed request
124- RAISE DEBUG ' s3_get, canonical_request: %' , canonical_request;
125- RAISE DEBUG ' s3_get, string_to_sign: %' , string_to_sign;
126- RAISE DEBUG ' s3_get, request %' , request;
174+ RAISE DEBUG ' s3_request, payload_hash: %' , payload_hash;
175+ RAISE DEBUG ' s3_request, canonical_request: %' , canonical_request;
176+ RAISE DEBUG ' s3_request, string_to_sign: %' , string_to_sign;
177+ RAISE DEBUG ' s3_request, request %' , request;
127178
128179 RETURN http(request);
180+ -- RETURN NULL;
129181
130182END;
131183$$ LANGUAGE ' plpgsql'
132184VOLATILE;
133-
134-
135-
136-
0 commit comments