16
16
17
17
package io .apicurio .registry .rest .v2 ;
18
18
19
+ import com .google .common .hash .Hashing ;
19
20
import io .apicurio .registry .auth .Authorized ;
20
21
import io .apicurio .registry .auth .AuthorizedLevel ;
21
22
import io .apicurio .registry .auth .AuthorizedStyle ;
27
28
import io .apicurio .registry .rest .HeadersHack ;
28
29
import io .apicurio .registry .rest .MissingRequiredParameterException ;
29
30
import io .apicurio .registry .rest .ParametersConflictException ;
31
+ import io .apicurio .registry .rest .RestConfig ;
30
32
import io .apicurio .registry .rest .v2 .beans .ArtifactMetaData ;
31
33
import io .apicurio .registry .rest .v2 .beans .ArtifactReference ;
32
34
import io .apicurio .registry .rest .v2 .beans .ArtifactSearchResults ;
69
71
import io .apicurio .registry .util .ContentTypeUtil ;
70
72
import io .apicurio .registry .utils .ArtifactIdValidator ;
71
73
import io .apicurio .registry .utils .IoUtil ;
74
+ import io .apicurio .registry .utils .JAXRSClientUtil ;
72
75
import org .jose4j .base64url .Base64 ;
73
76
74
77
import javax .enterprise .context .ApplicationScoped ;
75
78
import javax .inject .Inject ;
76
79
import javax .interceptor .Interceptors ;
77
80
import javax .servlet .http .HttpServletRequest ;
78
81
import javax .ws .rs .BadRequestException ;
82
+ import javax .ws .rs .client .Client ;
79
83
import javax .ws .rs .core .Context ;
80
84
import javax .ws .rs .core .MediaType ;
81
85
import javax .ws .rs .core .Response ;
86
+ import java .io .BufferedInputStream ;
82
87
import java .io .InputStream ;
88
+ import java .net .MalformedURLException ;
89
+ import java .net .URI ;
90
+ import java .net .URISyntaxException ;
91
+ import java .net .URL ;
92
+ import java .nio .charset .StandardCharsets ;
93
+ import java .security .KeyManagementException ;
94
+ import java .security .NoSuchAlgorithmException ;
83
95
import java .util .Collections ;
84
96
import java .util .HashSet ;
85
97
import java .util .List ;
94
106
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_DESCRIPTION ;
95
107
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_DESCRIPTION_ENCODED ;
96
108
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_EDITABLE_METADATA ;
109
+ import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_FROM_URL ;
97
110
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_GROUP_ID ;
98
111
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_IF_EXISTS ;
99
112
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_NAME ;
100
113
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_NAME_ENCODED ;
101
114
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_RULE ;
102
115
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_RULE_TYPE ;
116
+ import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_SHA ;
103
117
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_UPDATE_STATE ;
104
118
import static io .apicurio .common .apps .logging .audit .AuditingConstants .KEY_VERSION ;
105
119
@@ -131,6 +145,9 @@ public class GroupsResourceImpl implements GroupsResource {
131
145
@ Inject
132
146
ArtifactTypeUtilProviderFactory factory ;
133
147
148
+ @ Inject
149
+ RestConfig restConfig ;
150
+
134
151
/**
135
152
* @see io.apicurio.registry.rest.v2.GroupsResource#getLatestArtifact(java.lang.String, java.lang.String, java.lang.Boolean)
136
153
*/
@@ -585,30 +602,86 @@ public void deleteArtifactsInGroup(String groupId) {
585
602
}
586
603
587
604
/**
588
- * @see io.apicurio.registry.rest.v2.GroupsResource#createArtifact(String, ArtifactType, String, String, IfExists, Boolean, String, String, String, String, InputStream)
605
+ * @see io.apicurio.registry.rest.v2.GroupsResource#createArtifact(String, ArtifactType, String, String, IfExists, Boolean, String, String, String, String, String, String, InputStream)
589
606
*/
590
607
@ Override
591
- @ Audited (extractParameters = {"0" , KEY_GROUP_ID , "1" , KEY_ARTIFACT_TYPE , "2" , KEY_ARTIFACT_ID , "3" , KEY_VERSION , "4" , KEY_IF_EXISTS , "5" , KEY_CANONICAL , "6" , KEY_DESCRIPTION , "7" , KEY_DESCRIPTION_ENCODED , "8" , KEY_NAME , "9" , KEY_NAME_ENCODED })
608
+ @ Audited (extractParameters = {"0" , KEY_GROUP_ID , "1" , KEY_ARTIFACT_TYPE , "2" , KEY_ARTIFACT_ID , "3" , KEY_VERSION , "4" , KEY_IF_EXISTS , "5" , KEY_CANONICAL , "6" , KEY_DESCRIPTION , "7" , KEY_DESCRIPTION_ENCODED , "8" , KEY_NAME , "9" , KEY_NAME_ENCODED , "10" , KEY_FROM_URL , "11" , KEY_SHA })
592
609
@ Authorized (style = AuthorizedStyle .GroupOnly , level = AuthorizedLevel .Write )
593
610
public ArtifactMetaData createArtifact (String groupId , ArtifactType xRegistryArtifactType , String xRegistryArtifactId ,
594
611
String xRegistryVersion , IfExists ifExists , Boolean canonical ,
595
612
String xRegistryDescription , String xRegistryDescriptionEncoded ,
596
- String xRegistryName , String xRegistryNameEncoded , InputStream data ) {
597
-
598
- return this .createArtifactWithRefs (groupId , xRegistryArtifactType , xRegistryArtifactId , xRegistryVersion , ifExists , canonical , xRegistryDescription , xRegistryDescriptionEncoded , xRegistryName , xRegistryNameEncoded , data , Collections .emptyList ());
613
+ String xRegistryName , String xRegistryNameEncoded ,
614
+ String xRegistryContentHash , String xRegistryHashAlgorithm , InputStream data ) {
615
+ return this .createArtifactWithRefs (groupId , xRegistryArtifactType , xRegistryArtifactId , xRegistryVersion , ifExists , canonical , xRegistryDescription , xRegistryDescriptionEncoded , xRegistryName , xRegistryNameEncoded , xRegistryContentHash , xRegistryHashAlgorithm , data , Collections .emptyList ());
599
616
}
600
617
601
618
/**
602
- * @see io.apicurio.registry.rest.v2.GroupsResource#createArtifact(java.lang. String, io.apicurio.registry.types. ArtifactType, java.lang. String, java.lang. String, io.apicurio.registry.rest.v2.beans. IfExists, java.lang. Boolean, java.lang. String, java.lang. String, java.lang. String, java.lang. String, io.apicurio.registry.rest.v2.beans. ContentCreateRequest)
619
+ * @see io.apicurio.registry.rest.v2.GroupsResource#createArtifact(String, ArtifactType, String, String, IfExists, Boolean, String, String, String, String, String, String, ContentCreateRequest)
603
620
*/
604
621
@ Override
605
- @ Audited (extractParameters = {"0" , KEY_GROUP_ID , "1" , KEY_ARTIFACT_TYPE , "2" , KEY_ARTIFACT_ID , "3" , KEY_VERSION , "4" , KEY_IF_EXISTS , "5" , KEY_CANONICAL , "6" , KEY_DESCRIPTION , "7" , KEY_DESCRIPTION_ENCODED , "8" , KEY_NAME , "9" , KEY_NAME_ENCODED })
622
+ @ Audited (extractParameters = {"0" , KEY_GROUP_ID , "1" , KEY_ARTIFACT_TYPE , "2" , KEY_ARTIFACT_ID , "3" , KEY_VERSION , "4" , KEY_IF_EXISTS , "5" , KEY_CANONICAL , "6" , KEY_DESCRIPTION , "7" , KEY_DESCRIPTION_ENCODED , "8" , KEY_NAME , "9" , KEY_NAME_ENCODED , "10" , "from_url" /*KEY_FROM_URL*/ , "11" , "artifact_sha" /*KEY_SHA*/ })
606
623
@ Authorized (style = AuthorizedStyle .GroupOnly , level = AuthorizedLevel .Write )
607
- public ArtifactMetaData createArtifact (String groupId , ArtifactType xRegistryArtifactType ,
608
- String xRegistryArtifactId , String xRegistryVersion , IfExists ifExists , Boolean canonical ,
609
- String xRegistryDescription , String xRegistryDescriptionEncoded , String xRegistryName ,
610
- String xRegistryNameEncoded , ContentCreateRequest data ) {
611
- return this .createArtifactWithRefs (groupId , xRegistryArtifactType , xRegistryArtifactId , xRegistryVersion , ifExists , canonical , xRegistryDescription , xRegistryDescriptionEncoded , xRegistryName , xRegistryNameEncoded , IoUtil .toStream (data .getContent ()), data .getReferences ());
624
+ public ArtifactMetaData createArtifact (String groupId , ArtifactType xRegistryArtifactType , String xRegistryArtifactId ,
625
+ String xRegistryVersion , IfExists ifExists , Boolean canonical ,
626
+ String xRegistryDescription , String xRegistryDescriptionEncoded ,
627
+ String xRegistryName , String xRegistryNameEncoded ,
628
+ String xRegistryContentHash , String xRegistryHashAlgorithm , ContentCreateRequest data ) {
629
+ InputStream content = null ;
630
+ try {
631
+ URL url = new URL (data .getContent ());
632
+ content = fetchContentFromURL (url .toURI ());
633
+ } catch (MalformedURLException | URISyntaxException e ) {
634
+ content = IoUtil .toStream (data .getContent ());
635
+ }
636
+
637
+ return this .createArtifactWithRefs (groupId , xRegistryArtifactType , xRegistryArtifactId , xRegistryVersion , ifExists , canonical , xRegistryDescription , xRegistryDescriptionEncoded , xRegistryName , xRegistryNameEncoded , xRegistryContentHash , xRegistryHashAlgorithm , content , data .getReferences ());
638
+ }
639
+
640
+ public enum RegistryHashAlgorithm {
641
+ SHA256 ,
642
+ MD5
643
+ }
644
+
645
+ /**
646
+ * Return an InputStream for the resource to be downloaded
647
+ * @param url
648
+ */
649
+ private InputStream fetchContentFromURL (URI url ) {
650
+ Client client = null ;
651
+ try {
652
+ client = JAXRSClientUtil .getJAXRSClient (restConfig .getDownloadSkipSSLValidation ());
653
+ } catch (KeyManagementException kme ) {
654
+ throw new RuntimeException (kme );
655
+ } catch (NoSuchAlgorithmException nsae ) {
656
+ throw new RuntimeException (nsae );
657
+ }
658
+
659
+ // 1. Registry issues HTTP HEAD request to the target URL.
660
+ List <Object > contentLengthHeaders = client
661
+ .target (url )
662
+ .request ()
663
+ .head ()
664
+ .getHeaders ()
665
+ .get ("Content-Length" );
666
+
667
+ if (contentLengthHeaders == null || contentLengthHeaders .size () < 1 ) {
668
+ throw new BadRequestException ("Requested resource URL does not provide 'Content-Length' in the headers" );
669
+ }
670
+
671
+ // 2. According to HTTP specification, target server must return Content-Length header.
672
+ int contentLength = Integer .parseInt (contentLengthHeaders .get (0 ).toString ());
673
+
674
+ // 3. Registry analyzes value of Content-Length to check if file with declared size could be processed securely.
675
+ if (contentLength > restConfig .getDownloadMaxSize ()) {
676
+ throw new BadRequestException ("Requested resource is bigger than " + restConfig .getDownloadMaxSize () + " and cannot be downloaded." );
677
+ }
678
+
679
+ // 4. Finally, registry issues HTTP GET to the target URL and fetches only amount of bytes specified by HTTP HEAD from step 1.
680
+ return new BufferedInputStream (client
681
+ .target (url )
682
+ .request ()
683
+ .get ()
684
+ .readEntity (InputStream .class ), contentLength );
612
685
}
613
686
614
687
/**
@@ -623,13 +696,17 @@ public ArtifactMetaData createArtifact(String groupId, ArtifactType xRegistryArt
623
696
* @param xRegistryDescriptionEncoded
624
697
* @param xRegistryName
625
698
* @param xRegistryNameEncoded
699
+ * @param xRegistryContentHash
700
+ * @param xRegistryHashAlgorithm
626
701
* @param data
627
702
* @param references
628
703
*/
629
704
private ArtifactMetaData createArtifactWithRefs (String groupId , ArtifactType xRegistryArtifactType , String xRegistryArtifactId ,
630
705
String xRegistryVersion , IfExists ifExists , Boolean canonical ,
631
706
String xRegistryDescription , String xRegistryDescriptionEncoded ,
632
- String xRegistryName , String xRegistryNameEncoded , InputStream data , List <ArtifactReference > references ) {
707
+ String xRegistryName , String xRegistryNameEncoded ,
708
+ String xRegistryContentHash , String xRegistryHashAlgorithm ,
709
+ InputStream data , List <ArtifactReference > references ) {
633
710
634
711
requireParameter ("groupId" , groupId );
635
712
@@ -649,6 +726,25 @@ private ArtifactMetaData createArtifactWithRefs(String groupId, ArtifactType xRe
649
726
if (content .bytes ().length == 0 ) {
650
727
throw new BadRequestException (EMPTY_CONTENT_ERROR_MESSAGE );
651
728
}
729
+
730
+ // Mitigation for MITM attacks, verify that the artifact is the expected one
731
+ if (xRegistryContentHash != null ) {
732
+ String calculatedSha = null ;
733
+ RegistryHashAlgorithm algorithm = (xRegistryHashAlgorithm == null ) ? RegistryHashAlgorithm .SHA256 : RegistryHashAlgorithm .valueOf (xRegistryHashAlgorithm );
734
+ switch (algorithm ) {
735
+ case MD5 :
736
+ calculatedSha = Hashing .md5 ().hashString (content .content (), StandardCharsets .UTF_8 ).toString ();
737
+ break ;
738
+ case SHA256 :
739
+ calculatedSha = Hashing .sha256 ().hashString (content .content (), StandardCharsets .UTF_8 ).toString ();
740
+ break ;
741
+ }
742
+
743
+ if (!calculatedSha .equals (xRegistryContentHash .trim ())) {
744
+ throw new BadRequestException ("Provided Artifact Hash doesn't match with the content" );
745
+ }
746
+ }
747
+
652
748
final boolean fcanonical = canonical == null ? Boolean .FALSE : canonical ;
653
749
654
750
String ct = getContentType ();
@@ -660,7 +756,8 @@ private ArtifactMetaData createArtifactWithRefs(String groupId, ArtifactType xRe
660
756
} else if (!ArtifactIdValidator .isArtifactIdAllowed (artifactId )) {
661
757
throw new InvalidArtifactIdException (ArtifactIdValidator .ARTIFACT_ID_ERROR_MESSAGE );
662
758
}
663
- if (ContentTypeUtil .isApplicationYaml (ct )) {
759
+ if (ContentTypeUtil .isApplicationYaml (ct ) ||
760
+ (ContentTypeUtil .isApplicationCreateExtended (ct ) && ContentTypeUtil .isParsableYaml (content ))) {
664
761
content = ContentTypeUtil .yamlToJson (content );
665
762
}
666
763
0 commit comments