├── .gitignore ├── .project ├── LICENSE ├── README.md ├── pom.xml ├── sample-policy ├── bundle │ ├── .manifest │ └── bundle.tar.gz ├── src │ ├── build.sh │ ├── data.json │ └── policy.rego └── wasm │ ├── policy.wasm │ ├── policy_abi1_1.wasm │ └── policy_abi1_2.wasm └── src ├── main └── java │ └── io │ └── github │ └── sangkeon │ └── opa │ └── wasm │ ├── Bundle.java │ ├── BundleUtil.java │ ├── OPAAddr.java │ ├── OPAConstants.java │ ├── OPAErrorCode.java │ ├── OPAExports.java │ ├── OPAExportsAPI.java │ └── OPAModule.java └── test └── java └── UnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .classpath 15 | .settings/ 16 | bin/ 17 | target/ 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | my-app 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | 25 | 1614429659915 26 | 27 | 30 28 | 29 | org.eclipse.core.resources.regexFilterMatcher 30 | node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OPA(Open Policy Agent) WebAssembly SDK for Java 2 | 3 | ## About 4 | OPA(www.openpolicyagent.org) ia a awesome policy engine for Cloud Native Application and Micro Services. 5 | 6 | This project is a very sample-grade sdk to illustrate how to use wasm compiled rego policy for Java application. 7 | 8 | This project uses https://github.com/kawamuray/wasmtime-java for WASM runtime. 9 | 10 | Inspired by and, borrowed many ideas+codes from following projects. 11 | - golang-opa-wasm(https://github.com/open-policy-agent/golang-opa-wasm) 12 | - npm-opa-wasm(https://github.com/open-policy-agent/npm-opa-wasm) 13 | - dotnet-opa-wasm(https://github.com/christophwille/dotnet-opa-wasm) 14 | 15 | Tested under OPA version 0.26.0 16 | 17 | ## Warning 18 | - Very early stage project and almost sdk independent builtin functions not implemented(just place holder). 19 | - If you are using an Apple Silicon Mac, see [FOR_APPLE_SILICON_USERS.md](./FOR_APPLE_SILICON_USERS.md) first./del> 20 | 21 | ## Usage 22 | 23 | ### Maven dependency ### 24 | ``` 25 | 26 | io.github.sangkeon 27 | java-opa-wasm 28 | 0.2.5 29 | 30 | ``` 31 | 32 | ### To load and evaluate for OPA wasm file 33 | ``` 34 | try ( 35 | OPAModule om = new OPAModule("./sample-policy/wasm/policy.wasm"); 36 | ) { 37 | String input = "{\"user\": \"john\"}"; 38 | String data = "{\"role\":{\"john\":\"admin\"}}"; 39 | 40 | om.setData(data); 41 | 42 | String result = om.evaluate(input, "opa/wasm/test/allowed"); 43 | 44 | System.out.println("result=" + result); 45 | } 46 | ``` 47 | 48 | ### To load and evaluate for OPA bundle 49 | When using bundle, policy.wasm and data.json inside bundle will be loaded. 50 | 51 | ``` 52 | try { 53 | Bundle bundle = BundleUtil.extractBundle("./sample-policy/bundle/bundle.tar.gz"); 54 | 55 | try ( 56 | OPAModule om = new OPAModule(bundle); 57 | ) { 58 | String input = "{\"user\": \"alice\"}"; 59 | String result = om.evaluate(input, "opa/wasm/test/allowed"); 60 | 61 | System.out.println("result=" + result); 62 | } 63 | } catch(Exception e) { 64 | } 65 | 66 | ``` 67 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | io.github.sangkeon 5 | java-opa-wasm 6 | 0.2.5 7 | jar 8 | 9 | java-opa-wasm 10 | OPA(Open Policy Agent) WebAssembly SDK for Java 11 | http://github.com/sangkeon/java-opa-wasm 12 | 13 | 14 | 15 | The Apache Software License, Version 2.0 16 | http://www.apache.org/licenses/LICENSE-2.0.txt 17 | 18 | 19 | 20 | 21 | 22 | Sangkeon Lee 23 | sangkenlee@gmail.com 24 | 25 | 26 | 27 | 28 | scm:git:git://github.com/sangkeon/java-opa-wasm.git 29 | scm:git:ssh://github.com/sangkeon/java-opa-wasm.git 30 | http://github.com/sangkeon/java-opa-wasm/tree/main 31 | 32 | 33 | 34 | 1.8 35 | 1.8 36 | utf-8 37 | utf-8 38 | 39 | 40 | 41 | io.github.kawamuray.wasmtime 42 | wasmtime-java 43 | 0.18.0 44 | 45 | 46 | org.apache.commons 47 | commons-compress 48 | 1.26.0 49 | 50 | 51 | org.json 52 | json 53 | 20231013 54 | 55 | 56 | junit 57 | junit 58 | 4.13.2 59 | test 60 | 61 | 62 | 63 | 64 | ossrh 65 | https://s01.oss.sonatype.org/content/repositories/snapshots 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-compiler-plugin 73 | 3.11.0 74 | 75 | 76 | 77 | 78 | 79 | maven-javadoc-plugin 80 | 3.5.0 81 | 82 | -Xdoclint:none 83 | 84 | 85 | 86 | attach-javadocs 87 | 88 | jar 89 | 90 | 91 | 92 | 93 | 94 | maven-source-plugin 95 | 3.2.1 96 | 97 | 98 | attach-sources 99 | 100 | jar-no-fork 101 | 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-gpg-plugin 108 | 1.6 109 | 110 | 111 | sign-artifacts 112 | verify 113 | 114 | sign 115 | 116 | 117 | 118 | 119 | 120 | org.sonatype.plugins 121 | nexus-staging-maven-plugin 122 | 1.6.8 123 | true 124 | 125 | ossrh 126 | https://s01.oss.sonatype.org/ 127 | true 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | gpg 136 | ${env.MAVEN_GPG_PASSPHRASE} 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /sample-policy/bundle/.manifest: -------------------------------------------------------------------------------- 1 | {"revision":"","roots":[""],"wasm":[{"entrypoint":"opa/wasm/test/allowed","module":"/policy.wasm"}]} 2 | -------------------------------------------------------------------------------- /sample-policy/bundle/bundle.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangkeon/java-opa-wasm/82eaad0f89be6e3e0fdd680a78f6df223f6b1a25/sample-policy/bundle/bundle.tar.gz -------------------------------------------------------------------------------- /sample-policy/src/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | opa build -t wasm -o ../bundle/bundle.tar.gz -e opa/wasm/test/allowed policy.rego data.json 3 | tar xzf ../bundle/bundle.tar.gz --directory=../wasm /policy.wasm -------------------------------------------------------------------------------- /sample-policy/src/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "role" : { 3 | "alice" : "admin", 4 | "bob" : "user" 5 | } 6 | } -------------------------------------------------------------------------------- /sample-policy/src/policy.rego: -------------------------------------------------------------------------------- 1 | package opa.wasm.test 2 | 3 | default allowed = false 4 | 5 | allowed { 6 | user := input.user 7 | data.role[user] == "admin" 8 | } -------------------------------------------------------------------------------- /sample-policy/wasm/policy.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangkeon/java-opa-wasm/82eaad0f89be6e3e0fdd680a78f6df223f6b1a25/sample-policy/wasm/policy.wasm -------------------------------------------------------------------------------- /sample-policy/wasm/policy_abi1_1.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangkeon/java-opa-wasm/82eaad0f89be6e3e0fdd680a78f6df223f6b1a25/sample-policy/wasm/policy_abi1_1.wasm -------------------------------------------------------------------------------- /sample-policy/wasm/policy_abi1_2.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sangkeon/java-opa-wasm/82eaad0f89be6e3e0fdd680a78f6df223f6b1a25/sample-policy/wasm/policy_abi1_2.wasm -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/Bundle.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | public class Bundle { 4 | private byte[] policy; 5 | private String data; 6 | 7 | public Bundle() { 8 | } 9 | 10 | public Bundle(byte[] policy, String data) { 11 | this.policy = policy; 12 | this.data = data; 13 | } 14 | 15 | public byte[] getPolicy() { 16 | return policy; 17 | } 18 | 19 | public String getData() { 20 | return data; 21 | } 22 | 23 | public void setData(String data) { 24 | this.data = data; 25 | } 26 | 27 | public void setPolicy(byte[] policy) { 28 | this.policy = policy; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/BundleUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | import java.io.InputStream; 4 | import java.nio.file.Files; 5 | import java.nio.file.Paths; 6 | import java.io.IOException; 7 | import java.io.BufferedInputStream; 8 | 9 | import org.apache.commons.compress.archivers.ArchiveEntry; 10 | import org.apache.commons.compress.archivers.ArchiveInputStream; 11 | import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; 12 | import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; 13 | import org.apache.commons.compress.utils.IOUtils; 14 | 15 | public class BundleUtil { 16 | public static Bundle extractBundle(String filename) throws IOException { 17 | Bundle bundle = new Bundle(); 18 | 19 | try (InputStream fi = Files.newInputStream(Paths.get(filename)); 20 | InputStream bi = new BufferedInputStream(fi); 21 | InputStream gzi = new GzipCompressorInputStream(bi); 22 | ArchiveInputStream i = new TarArchiveInputStream(gzi) 23 | ) { 24 | ArchiveEntry entry = null; 25 | 26 | while ((entry = i.getNextEntry()) != null) { 27 | if (!i.canReadEntryData(entry)) { 28 | continue; 29 | } 30 | 31 | if("/policy.wasm".equals(entry.getName())) { 32 | bundle.setPolicy(IOUtils.toByteArray(i)); 33 | } else if("/data.json".equals(entry.getName())) { 34 | bundle.setData(new String(IOUtils.toByteArray(i))); 35 | } 36 | } 37 | } 38 | 39 | return bundle; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/OPAAddr.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | public class OPAAddr { 4 | private final int addr; 5 | 6 | private OPAAddr(int addr) { 7 | this.addr = addr; 8 | } 9 | 10 | public static OPAAddr newAddr(int addr) { 11 | return new OPAAddr(addr); 12 | } 13 | 14 | public OPAAddr copy() { 15 | return OPAAddr.newAddr(addr); 16 | } 17 | 18 | public int getInternal() { 19 | return addr; 20 | } 21 | 22 | public boolean isNull() { 23 | return addr == 0; 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/OPAConstants.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | public class OPAConstants { 4 | // For OPA WASM imports 5 | public static final String MODULE = "env"; 6 | public static final String MEMORY = "memory"; 7 | 8 | public static final String OPA_ABORT = "opa_abort"; 9 | public static final String OPA_BUILTIN0 = "opa_builtin0"; 10 | public static final String OPA_BUILTIN1 = "opa_builtin1"; 11 | public static final String OPA_BUILTIN2 = "opa_builtin2"; 12 | public static final String OPA_BUILTIN3 = "opa_builtin3"; 13 | public static final String OPA_BUILTIN4 = "opa_builtin4"; 14 | public static final String OPA_PRINTLN = "opa_println"; 15 | 16 | // For OPA WASM exports 17 | public static final String OPA_MALLOC = "opa_malloc"; 18 | public static final String OPA_HEAP_PTR_GET = "opa_heap_ptr_get"; 19 | public static final String OPA_HEAP_PTR_SET = "opa_heap_ptr_set"; 20 | public static final String OPA_JSON_DUMP = "opa_json_dump"; 21 | public static final String OPA_JSON_PARSE = "opa_json_parse"; 22 | public static final String OPA_EVAL_CTX_NEW = "opa_eval_ctx_new"; 23 | public static final String OPA_EVAL_CTX_SET_INPUT = "opa_eval_ctx_set_input"; 24 | public static final String OPA_EVAL_CTX_SET_DATA = "opa_eval_ctx_set_data"; 25 | public static final String OPA_EVAL_CTX_GET_RESULT = "opa_eval_ctx_get_result"; 26 | public static final String BUILTINS = "builtins"; 27 | public static final String EVAL = "eval"; 28 | public static final String ENTRYPOINTS = "entrypoints"; 29 | public static final String OPA_EVAL_CTX_SET_ENTRYPOINT = "opa_eval_ctx_set_entrypoint"; 30 | public static final String OPA_FREE = "opa_free"; 31 | public static final String OPA_VALUE_PARSE = "opa_value_parse"; 32 | public static final String OPA_VALUE_DUMP = "opa_value_dump"; 33 | public static final String OPA_VALUE_ADD_PATH = "opa_value_add_path"; 34 | public static final String OPA_VALUE_REMOVE_PATH = "opa_value_remove_path"; 35 | 36 | public static final String OPA_EVAL = "opa_eval"; 37 | 38 | public static final String OPA_WASM_ABI_VERSION = "opa_wasm_abi_version"; 39 | public static final String OPA_WASM_ABI_MINOR_VERSION = "opa_wasm_abi_minor_version"; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/OPAErrorCode.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | public enum OPAErrorCode { 4 | OPA_ERR_OK(0), OPA_ERR_INTERNAL(1), OPA_ERR_INVALID_TYPE(2), OPA_ERR_INVALID_PATH(3); 5 | 6 | OPAErrorCode(int value) { 7 | this.value = value; 8 | } 9 | 10 | private final int value; 11 | 12 | public int value() { 13 | return value; 14 | } 15 | 16 | public String message() { 17 | switch (this.value) { 18 | case 1: 19 | return "internal error"; 20 | case 2: 21 | return "invalid type"; 22 | case 3: 23 | return "invalid path"; 24 | } 25 | 26 | return "ok"; 27 | } 28 | 29 | public static OPAErrorCode fromValue(int x) { 30 | switch (x) { 31 | case 0: 32 | return OPA_ERR_OK; 33 | case 1: 34 | return OPA_ERR_INTERNAL; 35 | case 2: 36 | return OPA_ERR_INVALID_TYPE; 37 | case 3: 38 | return OPA_ERR_INVALID_PATH; 39 | } 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/OPAExports.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | import static io.github.kawamuray.wasmtime.WasmValType.I32; 4 | 5 | import java.util.Optional; 6 | 7 | import io.github.kawamuray.wasmtime.*; 8 | 9 | public class OPAExports implements OPAExportsAPI, Disposable { 10 | private Linker linker; 11 | private Store store; 12 | 13 | private Func opaMallocFn = null; 14 | private Func opaHeapPtrGetFn = null; 15 | private Func opaHeapPtrSetFn = null; 16 | private Func opaJsonDumpFn = null; 17 | private Func opaJsonParseFn = null; 18 | private Func opaEvalCtxNewFn = null; 19 | private Func opaEvalCtxSetInputFn = null; 20 | private Func opaEvalCtxSetDataFn = null; 21 | private Func opaEvalCtxGetResultFn = null; 22 | private Func builtinsFn = null; 23 | private Func evalFn = null; 24 | private Func entrypointsFn = null; 25 | private Func opaEvalCtxSetEntryPointFn = null; 26 | private Func opaFreeFn = null; 27 | private Func opaValueParseFn = null; 28 | private Func opaValueDumpFn = null; 29 | private Func opaValueAddPathFn = null; 30 | private Func opaValueRemovePathFn = null; 31 | private Func opaEvalFn = null; 32 | private Integer abiMajorVersion = null; 33 | private Integer abiMinorVersion = null; 34 | 35 | private OPAExports(Linker linker, String moduleName, Store store) { 36 | this.linker = linker; 37 | this.store = store; 38 | 39 | initFns(moduleName); 40 | } 41 | 42 | public static OPAExportsAPI getOPAExports(Linker linker, String moduleName, Store store) { 43 | return new OPAExports(linker, moduleName, store); 44 | } 45 | 46 | public void initFns(String moduleName) { 47 | opaMallocFn = linker.get(store, moduleName, OPAConstants.OPA_MALLOC).get().func(); 48 | opaHeapPtrGetFn = linker.get(store, moduleName, OPAConstants.OPA_HEAP_PTR_GET).get().func(); 49 | opaHeapPtrSetFn = linker.get(store, moduleName, OPAConstants.OPA_HEAP_PTR_SET).get().func(); 50 | opaJsonDumpFn = linker.get(store, moduleName, OPAConstants.OPA_JSON_DUMP).get().func(); 51 | opaJsonParseFn = linker.get(store, moduleName, OPAConstants.OPA_JSON_PARSE).get().func(); 52 | opaEvalCtxNewFn = linker.get(store, moduleName, OPAConstants.OPA_EVAL_CTX_NEW).get().func(); 53 | opaEvalCtxSetInputFn = linker.get(store, moduleName, OPAConstants.OPA_EVAL_CTX_SET_INPUT).get().func(); 54 | opaEvalCtxSetDataFn = linker.get(store, moduleName, OPAConstants.OPA_EVAL_CTX_SET_DATA).get().func(); 55 | opaEvalCtxGetResultFn = linker.get(store, moduleName, OPAConstants.OPA_EVAL_CTX_GET_RESULT).get().func(); 56 | builtinsFn = linker.get(store, moduleName, OPAConstants.BUILTINS).get().func(); 57 | evalFn = linker.get(store, moduleName, OPAConstants.EVAL).get().func(); 58 | entrypointsFn = linker.get(store, moduleName, OPAConstants.ENTRYPOINTS).get().func(); 59 | opaEvalCtxSetEntryPointFn = linker.get(store, moduleName, OPAConstants.OPA_EVAL_CTX_SET_ENTRYPOINT).get().func(); 60 | opaFreeFn = linker.get(store, moduleName, OPAConstants.OPA_FREE).get().func(); 61 | opaValueParseFn = linker.get(store, moduleName, OPAConstants.OPA_VALUE_PARSE).get().func(); 62 | opaValueDumpFn = linker.get(store, moduleName, OPAConstants.OPA_VALUE_DUMP).get().func(); 63 | opaValueAddPathFn = linker.get(store, moduleName, OPAConstants.OPA_VALUE_ADD_PATH).get().func(); 64 | opaValueRemovePathFn = linker.get(store, moduleName, OPAConstants.OPA_VALUE_REMOVE_PATH).get().func(); 65 | 66 | Optional opaEvalExtern = linker.get(store, moduleName, OPAConstants.OPA_EVAL); 67 | 68 | if(opaEvalExtern.isPresent()) { 69 | opaEvalFn = opaEvalExtern.get().func(); 70 | } 71 | 72 | if(linker.get(store, moduleName, OPAConstants.OPA_WASM_ABI_VERSION).isPresent()) { 73 | Global majorVersion = linker.get(store, moduleName, OPAConstants.OPA_WASM_ABI_VERSION).get().global(); 74 | 75 | this.abiMajorVersion = majorVersion.get(store).i32(); 76 | 77 | majorVersion.dispose(); 78 | } 79 | 80 | if(linker.get(store, moduleName, OPAConstants.OPA_WASM_ABI_MINOR_VERSION).isPresent()) { 81 | Global minorVersion = linker.get(store, moduleName, OPAConstants.OPA_WASM_ABI_MINOR_VERSION).get().global(); 82 | 83 | this.abiMinorVersion = minorVersion.get(store).i32(); 84 | 85 | minorVersion.dispose(); 86 | } 87 | } 88 | 89 | public void disposeFns() { 90 | if(opaMallocFn != null) { 91 | opaMallocFn.dispose(); 92 | opaMallocFn = null; 93 | } 94 | 95 | if(opaHeapPtrGetFn != null) { 96 | opaHeapPtrGetFn.dispose(); 97 | opaHeapPtrGetFn = null; 98 | } 99 | 100 | if(opaHeapPtrSetFn != null) { 101 | opaHeapPtrSetFn.dispose(); 102 | opaHeapPtrSetFn = null; 103 | } 104 | 105 | if(opaJsonDumpFn != null) { 106 | opaJsonDumpFn.dispose(); 107 | opaJsonDumpFn = null; 108 | } 109 | 110 | if(opaJsonParseFn != null) { 111 | opaJsonParseFn.dispose(); 112 | opaJsonParseFn = null; 113 | } 114 | 115 | if(opaEvalCtxNewFn != null) { 116 | opaEvalCtxNewFn.dispose(); 117 | opaEvalCtxNewFn = null; 118 | } 119 | 120 | if(opaEvalCtxSetInputFn != null) { 121 | opaEvalCtxSetInputFn.dispose(); 122 | opaEvalCtxSetInputFn = null; 123 | } 124 | 125 | if(opaEvalCtxSetDataFn != null) { 126 | opaEvalCtxSetDataFn.dispose(); 127 | opaEvalCtxSetDataFn = null; 128 | } 129 | 130 | if(opaEvalCtxGetResultFn != null) { 131 | opaEvalCtxGetResultFn.dispose(); 132 | opaEvalCtxGetResultFn = null; 133 | } 134 | 135 | if(builtinsFn != null) { 136 | builtinsFn.dispose(); 137 | builtinsFn = null; 138 | } 139 | 140 | if(evalFn != null) { 141 | evalFn.dispose(); 142 | evalFn = null; 143 | } 144 | 145 | if(entrypointsFn != null) { 146 | entrypointsFn.dispose(); 147 | entrypointsFn = null; 148 | } 149 | 150 | if(opaEvalCtxSetEntryPointFn != null) { 151 | opaEvalCtxSetEntryPointFn.dispose(); 152 | opaEvalCtxSetEntryPointFn = null; 153 | } 154 | 155 | if(opaFreeFn != null) { 156 | opaFreeFn.dispose(); 157 | opaFreeFn = null; 158 | } 159 | 160 | if(opaValueParseFn != null) { 161 | opaValueParseFn.dispose(); 162 | opaValueParseFn = null; 163 | } 164 | 165 | if(opaValueDumpFn != null) { 166 | opaValueDumpFn.dispose(); 167 | opaValueDumpFn = null; 168 | } 169 | 170 | if(opaValueAddPathFn != null) { 171 | opaValueAddPathFn.dispose(); 172 | opaValueAddPathFn = null; 173 | } 174 | 175 | if(opaValueRemovePathFn != null) { 176 | opaValueRemovePathFn.dispose(); 177 | opaValueRemovePathFn = null; 178 | } 179 | 180 | if(opaEvalFn != null) { 181 | opaEvalFn.dispose(); 182 | opaEvalFn = null; 183 | } 184 | } 185 | 186 | public void dispose() { 187 | disposeFns(); 188 | } 189 | 190 | @Override 191 | public OPAAddr opaMalloc(int bytes) { 192 | WasmFunctions.Function1 opa_malloc = WasmFunctions.func(store, opaMallocFn, I32, I32); 193 | int addr = opa_malloc.call(bytes); 194 | 195 | return OPAAddr.newAddr(addr); 196 | } 197 | 198 | @Override 199 | public OPAAddr opaHeapPtrGet() { 200 | WasmFunctions.Function0 opa_heap_ptr_get = WasmFunctions.func(store, opaHeapPtrGetFn, I32); 201 | int addr = opa_heap_ptr_get.call(); 202 | 203 | return OPAAddr.newAddr(addr); 204 | } 205 | 206 | @Override 207 | public void opaHeapPtrSet(OPAAddr addr) { 208 | WasmFunctions.Consumer1 opa_heap_ptr_set = WasmFunctions.consumer(store, opaHeapPtrSetFn, I32); 209 | opa_heap_ptr_set.accept(addr.getInternal()); 210 | } 211 | 212 | @Override 213 | public OPAAddr opaJsonDump(OPAAddr valueAddr) { 214 | WasmFunctions.Function1 opa_json_dump = WasmFunctions.func(store, opaJsonDumpFn, I32, I32); 215 | int strAddr = opa_json_dump.call(valueAddr.getInternal()); 216 | 217 | return OPAAddr.newAddr(strAddr); 218 | } 219 | 220 | @Override 221 | public OPAAddr opaJsonParse(OPAAddr addr, int jsonLength) { 222 | WasmFunctions.Function2 opa_json_parse = WasmFunctions.func(store, opaJsonParseFn, I32, I32, I32); 223 | int valueAddr = opa_json_parse.call(addr.getInternal(), jsonLength); 224 | 225 | return OPAAddr.newAddr(valueAddr); 226 | } 227 | 228 | @Override 229 | public OPAAddr opaEvalCtxNew() { 230 | WasmFunctions.Function0 opa_eval_ctx_new = WasmFunctions.func(store, opaEvalCtxNewFn, I32); 231 | int ctxAddr = opa_eval_ctx_new.call(); 232 | 233 | return OPAAddr.newAddr(ctxAddr); 234 | } 235 | 236 | @Override 237 | public void opaEvalCtxSetInput(OPAAddr ctxAddr, OPAAddr inputAddr) { 238 | WasmFunctions.Consumer2 opa_eval_ctx_set_input = WasmFunctions.consumer(store, opaEvalCtxSetInputFn, I32, I32); 239 | opa_eval_ctx_set_input.accept(ctxAddr.getInternal(), inputAddr.getInternal()); 240 | } 241 | 242 | @Override 243 | public void opaEvalCtxSetData(OPAAddr ctxAddr, OPAAddr dataAddr) { 244 | WasmFunctions.Consumer2 opa_eval_ctx_set_data = WasmFunctions.consumer(store, opaEvalCtxSetDataFn, I32, I32); 245 | opa_eval_ctx_set_data.accept(ctxAddr.getInternal(), dataAddr.getInternal()); 246 | } 247 | 248 | @Override 249 | public OPAAddr opaEvalCtxGetResult(OPAAddr ctxAddr) { 250 | WasmFunctions.Function1 opa_eval_ctx_get_result = WasmFunctions.func(store, opaEvalCtxGetResultFn, I32, I32); 251 | int valueAddr = opa_eval_ctx_get_result.call(ctxAddr.getInternal()); 252 | 253 | return OPAAddr.newAddr(valueAddr); 254 | } 255 | 256 | @Override 257 | public OPAAddr builtins() { 258 | WasmFunctions.Function0 builtins = WasmFunctions.func(store, builtinsFn, I32); 259 | int valueAddr = builtins.call(); 260 | 261 | return OPAAddr.newAddr(valueAddr); 262 | } 263 | 264 | @Override 265 | public OPAErrorCode eval(OPAAddr ctxAddr) { 266 | WasmFunctions.Function1 eval = WasmFunctions.func(store, evalFn, I32, I32); 267 | int errorCode = eval.call(ctxAddr.getInternal()); 268 | 269 | return OPAErrorCode.fromValue(errorCode); 270 | } 271 | 272 | @Override 273 | public OPAAddr entrypoints() { 274 | WasmFunctions.Function0 entrypoints = WasmFunctions.func(store, entrypointsFn, I32); 275 | int valueAddr = entrypoints.call(); 276 | 277 | return OPAAddr.newAddr(valueAddr); 278 | } 279 | 280 | @Override 281 | public void opaEvalCtxSetEntryPoint(OPAAddr ctxAddr, int entrypoint_id) { 282 | WasmFunctions.Consumer2 opa_eval_ctx_set_entrypoint = WasmFunctions.consumer(store, opaEvalCtxSetEntryPointFn, I32, I32); 283 | opa_eval_ctx_set_entrypoint.accept(ctxAddr.getInternal(), entrypoint_id); 284 | } 285 | 286 | @Override 287 | public void opaFree(OPAAddr addr) { 288 | WasmFunctions.Consumer1 opa_free = WasmFunctions.consumer(store, opaFreeFn, I32); 289 | opa_free.accept(addr.getInternal()); 290 | } 291 | 292 | @Override 293 | public OPAAddr opaValueParse(OPAAddr addr, int jsonLength) { 294 | WasmFunctions.Function2 opa_value_parse = WasmFunctions.func(store, opaValueParseFn, I32, I32, I32); 295 | int valueAddr = opa_value_parse.call(addr.getInternal(), jsonLength); 296 | 297 | return OPAAddr.newAddr(valueAddr); 298 | } 299 | 300 | @Override 301 | public OPAAddr opaValueDump(OPAAddr valueAddr) { 302 | WasmFunctions.Function1 opa_value_dump = WasmFunctions.func(store, opaValueDumpFn, I32, I32); 303 | int strAddr = opa_value_dump.call(valueAddr.getInternal()); 304 | 305 | return OPAAddr.newAddr(strAddr); 306 | } 307 | 308 | @Override 309 | public OPAErrorCode opaValueAddPath(OPAAddr baseValueAddr, OPAAddr pathValueAddr, OPAAddr valueAddr) { 310 | WasmFunctions.Function3 opa_value_add_path = WasmFunctions.func(store, opaValueAddPathFn, I32, I32, I32, I32); 311 | int errorCode = opa_value_add_path.call(baseValueAddr.getInternal(), pathValueAddr.getInternal(), valueAddr.getInternal()); 312 | 313 | return OPAErrorCode.fromValue(errorCode); 314 | } 315 | 316 | @Override 317 | public OPAErrorCode opaValueRemovePath(OPAAddr baseValueAddr, OPAAddr pathValueAddr) { 318 | WasmFunctions.Function2 opa_value_remove_path = WasmFunctions.func(store, opaValueRemovePathFn, I32, I32, I32); 319 | int errorCode = opa_value_remove_path.call(baseValueAddr.getInternal(), pathValueAddr.getInternal()); 320 | 321 | return OPAErrorCode.fromValue(errorCode); 322 | } 323 | 324 | @Override 325 | public OPAAddr opaEval(OPAAddr reservedAddr, int entrypoint_id, OPAAddr valueAddr, OPAAddr strAddr, int length, OPAAddr heapAddr, int format) { 326 | if(opaEvalFn == null) { 327 | throw new UnsupportedOperationException("opa_eval not supported, may be compiled using unsupported ABI(<1.2)"); 328 | } 329 | 330 | WasmFunctions.Function7 opa_eval = WasmFunctions.func(store, opaEvalFn, I32, I32, I32, I32, I32, I32, I32, I32); 331 | 332 | int resultStrAddr = opa_eval.call(reservedAddr.getInternal(), entrypoint_id, valueAddr.getInternal(), strAddr.getInternal(), length, heapAddr.getInternal(), format); 333 | 334 | return OPAAddr.newAddr(resultStrAddr); 335 | } 336 | 337 | @Override 338 | public boolean isFastPathEvalSupported() { 339 | return (opaEvalFn != null); 340 | } 341 | 342 | @Override 343 | public Integer getAbiMajorVersion() { return abiMajorVersion; } 344 | 345 | @Override 346 | public Integer getAbiMinorVersion() { return abiMinorVersion; } 347 | } 348 | -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/OPAExportsAPI.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | public interface OPAExportsAPI { 4 | public OPAErrorCode eval(OPAAddr ctxAddr); 5 | 6 | public OPAAddr builtins(); 7 | 8 | public OPAAddr entrypoints(); 9 | 10 | public OPAAddr opaEvalCtxNew(); 11 | 12 | public void opaEvalCtxSetInput(OPAAddr ctxAddr, OPAAddr inputAddr); 13 | 14 | public void opaEvalCtxSetData(OPAAddr ctxAddr, OPAAddr dataAddr); 15 | 16 | public void opaEvalCtxSetEntryPoint(OPAAddr ctxAddr, int entrypoint_id); 17 | 18 | public OPAAddr opaEvalCtxGetResult(OPAAddr ctxAddr); 19 | 20 | public OPAAddr opaMalloc(int bytes); 21 | 22 | public void opaFree(OPAAddr addr); 23 | 24 | public OPAAddr opaJsonParse(OPAAddr addr, int jsonLength); 25 | 26 | public OPAAddr opaValueParse(OPAAddr addr, int jsonLength); 27 | 28 | public OPAAddr opaJsonDump(OPAAddr valueAddr); 29 | 30 | public OPAAddr opaValueDump(OPAAddr valueAddr); 31 | 32 | public void opaHeapPtrSet(OPAAddr addr); 33 | 34 | public OPAAddr opaHeapPtrGet(); 35 | 36 | public OPAErrorCode opaValueAddPath(OPAAddr baseValueAddr, OPAAddr pathValueAddr, OPAAddr valueAddr); 37 | 38 | public OPAErrorCode opaValueRemovePath(OPAAddr baseValueAddr, OPAAddr pathValueAddr); 39 | 40 | public OPAAddr opaEval(OPAAddr reservedAddr, int entrypoint_id, OPAAddr valueAddr, OPAAddr strAddr, int length, OPAAddr heapAddr, int format); 41 | 42 | public boolean isFastPathEvalSupported(); 43 | 44 | public Integer getAbiMajorVersion(); 45 | 46 | public Integer getAbiMinorVersion(); 47 | } -------------------------------------------------------------------------------- /src/main/java/io/github/sangkeon/opa/wasm/OPAModule.java: -------------------------------------------------------------------------------- 1 | package io.github.sangkeon.opa.wasm; 2 | 3 | import static io.github.kawamuray.wasmtime.WasmValType.I32; 4 | 5 | import java.io.UnsupportedEncodingException; 6 | import java.nio.ByteBuffer; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Iterator; 11 | 12 | import io.github.kawamuray.wasmtime.Linker; 13 | 14 | import io.github.kawamuray.wasmtime.Extern; 15 | import io.github.kawamuray.wasmtime.Func; 16 | import io.github.kawamuray.wasmtime.Memory; 17 | import io.github.kawamuray.wasmtime.Disposable; 18 | import io.github.kawamuray.wasmtime.MemoryType; 19 | import io.github.kawamuray.wasmtime.Module; 20 | import io.github.kawamuray.wasmtime.Store; 21 | import io.github.kawamuray.wasmtime.WasmFunctions; 22 | import io.github.kawamuray.wasmtime.wasi.WasiCtx; 23 | import io.github.kawamuray.wasmtime.wasi.WasiCtxBuilder; 24 | 25 | import org.json.JSONObject; 26 | 27 | public class OPAModule implements Disposable { 28 | public static final String EMPTY_JSON = "{}"; 29 | public static final String MODULE_NAME = "policy"; 30 | private Map builtinFunc = new HashMap<>(); 31 | private Map entrypoints = new HashMap<>(); 32 | 33 | private Store store; 34 | private Linker linker; 35 | private WasiCtx wasi; 36 | 37 | private OPAExportsAPI exports; 38 | 39 | private Func abort; 40 | private Func println; 41 | private Func builtin0; 42 | private Func builtin1; 43 | private Func builtin2; 44 | private Func builtin3; 45 | private Func builtin4; 46 | private Memory memory; 47 | private Module module; 48 | 49 | private OPAAddr _dataAddr; 50 | private OPAAddr _baseHeapPtr; 51 | private OPAAddr _dataHeapPtr; 52 | private OPAAddr _heapPtr; 53 | 54 | public OPAModule(String filename) { 55 | this(filename, EMPTY_JSON); 56 | } 57 | 58 | public OPAModule(String filename, String json) { 59 | wasi = new WasiCtxBuilder().inheritStdout().inheritStderr().build(); 60 | store = Store.withoutData(); 61 | linker = new Linker(store.engine()); 62 | module = Module.fromFile(store.engine(), filename); 63 | 64 | initImports(); 65 | 66 | WasiCtx.addToLinker(linker); 67 | 68 | linker.module(store, MODULE_NAME, module); 69 | 70 | exports = OPAExports.getOPAExports(linker, MODULE_NAME, store); 71 | 72 | loadBuiltins(); 73 | loadEntrypoints(); 74 | 75 | _dataAddr = loadJson(json); 76 | _baseHeapPtr = exports.opaHeapPtrGet(); 77 | _dataHeapPtr = _baseHeapPtr.copy(); 78 | _heapPtr = _baseHeapPtr.copy(); 79 | } 80 | 81 | public OPAModule(Bundle bundle) { 82 | wasi = new WasiCtxBuilder().inheritStdout().inheritStderr().build(); 83 | store = Store.withoutData(wasi); 84 | linker = new Linker(store.engine()); 85 | module = Module.fromBinary(store.engine(), bundle.getPolicy()); 86 | 87 | initImports(); 88 | 89 | WasiCtx.addToLinker(linker); 90 | 91 | linker.module(store, MODULE_NAME, module); 92 | 93 | exports = OPAExports.getOPAExports(linker, MODULE_NAME, store); 94 | 95 | loadBuiltins(); 96 | loadEntrypoints(); 97 | 98 | _dataAddr = loadJson(bundle.getData()); 99 | _baseHeapPtr = exports.opaHeapPtrGet(); 100 | _dataHeapPtr = _baseHeapPtr.copy(); 101 | _heapPtr = _baseHeapPtr.copy(); 102 | } 103 | 104 | public Map getEntrypoints() { 105 | return entrypoints; 106 | } 107 | 108 | public void loadEntrypoints() { 109 | entrypoints.clear(); 110 | 111 | OPAAddr entrypointsAddr = exports.entrypoints(); 112 | 113 | if(!entrypointsAddr.isNull()) { 114 | String jsonString = dumpJson(entrypointsAddr); 115 | 116 | JSONObject jObject = new JSONObject(jsonString); 117 | 118 | Iterator iter = jObject.keys(); 119 | 120 | while(iter.hasNext()) { 121 | String key = iter.next(); 122 | int val = jObject.getInt(key); 123 | 124 | entrypoints.put(key, val); 125 | } 126 | } 127 | } 128 | 129 | public void loadBuiltins() { 130 | builtinFunc.clear(); 131 | 132 | OPAAddr builtinaddr = exports.builtins(); 133 | 134 | if(!builtinaddr.isNull()) { 135 | String jsonString = dumpJson(builtinaddr); 136 | 137 | JSONObject jObject = new JSONObject(jsonString); 138 | 139 | Iterator iter = jObject.keys(); 140 | 141 | while(iter.hasNext()) { 142 | String key = iter.next(); 143 | int val = jObject.getInt(key); 144 | 145 | builtinFunc.put(val, key); 146 | } 147 | } 148 | } 149 | 150 | public String getFuncName(int id) { 151 | return builtinFunc.get(id); 152 | } 153 | 154 | public void dispose() { 155 | dispose(true); 156 | } 157 | 158 | public String readStringFromOPAMemory(OPAAddr addr) { 159 | return decodeNullTerminatedString(memory, addr); 160 | } 161 | 162 | public String evaluate(String json) { 163 | // Evaluate with default entrypoint 164 | return evaluate(json, 0); 165 | } 166 | 167 | public String evaluate(String json, String entrypoint) { 168 | if(entrypoints.containsKey(entrypoint)) { 169 | return evaluate(json, entrypoints.get(entrypoint)); 170 | } 171 | 172 | throw new RuntimeException(String.format("entrypoint %s is not valid", entrypoint)); 173 | } 174 | 175 | public String evaluate(String json, int entrypoint) { 176 | _heapPtr = _dataHeapPtr.copy(); 177 | 178 | if(exports.isFastPathEvalSupported()) { 179 | return evaluateFastPath(json, entrypoint); 180 | } else { 181 | return evaluateNormalPath(json, entrypoint); 182 | } 183 | } 184 | 185 | private String evaluateNormalPath(String json, int entrypoint) { 186 | // Reset the heap pointer before each evaluation 187 | exports.opaHeapPtrSet(_dataHeapPtr); 188 | 189 | // Load the input data 190 | OPAAddr inputAddr = loadJson(json); 191 | 192 | // Setup the evaluation context 193 | OPAAddr ctxAddr = exports.opaEvalCtxNew(); 194 | exports.opaEvalCtxSetInput(ctxAddr, inputAddr); 195 | exports.opaEvalCtxSetData(ctxAddr, _dataAddr); 196 | exports.opaEvalCtxSetEntryPoint(ctxAddr, entrypoint); 197 | 198 | // Actually evaluate the policy 199 | OPAErrorCode err = exports.eval(ctxAddr); 200 | 201 | if(err != OPAErrorCode.OPA_ERR_OK) { 202 | throw new RuntimeException(String.format("evaluate error: %s", err.message())); 203 | } 204 | 205 | // Retrieve the result 206 | OPAAddr resultAddr = exports.opaEvalCtxGetResult(ctxAddr); 207 | return dumpJson(resultAddr); 208 | } 209 | 210 | private String evaluateFastPath(String json, int entrypoint) { 211 | byte[] jsonBytes = json.getBytes(); 212 | int size = jsonBytes.length; 213 | 214 | ByteBuffer buf = memory.buffer(store); 215 | OPAAddr inputAddr = _heapPtr.copy(); 216 | for(int i = 0; i < size; i++) { 217 | buf.put(inputAddr.getInternal() + i, jsonBytes[i]); 218 | } 219 | 220 | this._heapPtr = OPAAddr.newAddr(inputAddr.getInternal() + size); 221 | 222 | OPAAddr resultAddr = exports.opaEval(OPAAddr.newAddr(0), entrypoint, this._dataAddr, 223 | inputAddr, jsonBytes.length, _heapPtr ,0); 224 | 225 | return decodeNullTerminatedString(memory, resultAddr); 226 | } 227 | 228 | public void setData(String json) { 229 | exports.opaHeapPtrSet(_baseHeapPtr); 230 | _dataAddr = loadJson(json); 231 | _dataHeapPtr = exports.opaHeapPtrGet(); 232 | _heapPtr = _dataHeapPtr.copy(); 233 | } 234 | 235 | public OPAAddr loadJson(String json) { 236 | OPAAddr addr = writeString(memory, json); 237 | 238 | OPAAddr parseAddr = exports.opaJsonParse(addr, json.length()); 239 | 240 | if (parseAddr.isNull()) { 241 | throw new NullPointerException("Parsing failed"); 242 | } 243 | 244 | return parseAddr; 245 | } 246 | 247 | private OPAAddr writeString(Memory memory, String string) { 248 | byte[] stringBytes = string.getBytes(); 249 | 250 | OPAAddr addr = exports.opaMalloc(stringBytes.length); 251 | 252 | ByteBuffer buf = memory.buffer(store); 253 | 254 | int internalAddr = addr.getInternal(); 255 | 256 | for(int i = 0; i < stringBytes.length; i++ ) { 257 | buf.put(internalAddr + i, stringBytes[i]); 258 | } 259 | 260 | return addr; 261 | } 262 | 263 | private String dumpJson(OPAAddr addrResult) { 264 | OPAAddr addr = exports.opaJsonDump(addrResult); 265 | return decodeNullTerminatedString(memory, addr); 266 | } 267 | 268 | private String decodeNullTerminatedString(Memory memory, OPAAddr addr) { 269 | int internalAddr = addr.getInternal(); 270 | int end = internalAddr; 271 | 272 | ByteBuffer buf = memory.buffer(store); 273 | 274 | while(buf.get(end) != 0) { 275 | end++; 276 | } 277 | 278 | int size = end - internalAddr; 279 | 280 | if (size == 0) { 281 | return ""; 282 | } 283 | 284 | byte[] result = new byte[size]; 285 | 286 | for(int i = 0; i < size; i++) { 287 | result[i] = buf.get(internalAddr + i); 288 | } 289 | 290 | return new String(result); 291 | } 292 | 293 | private void initImports() { 294 | memory = new Memory(store, new MemoryType(5L, false)); 295 | 296 | abort = WasmFunctions.wrap(store, I32, (addr) -> { 297 | throw new RuntimeException(readStringFromOPAMemory(OPAAddr.newAddr(addr))); 298 | }); 299 | 300 | println = WasmFunctions.wrap(store, I32, (addr) -> { 301 | System.out.println(readStringFromOPAMemory(OPAAddr.newAddr(addr))); 302 | }); 303 | 304 | builtin0 = WasmFunctions.wrap(store, I32, I32, I32, 305 | (builtinId, opaCtxReserved) -> { 306 | String funcName = getFuncName(builtinId); 307 | 308 | checkBuiltinFunctionExists(builtinId, funcName); 309 | 310 | unsupportedFunction(builtinId, funcName); 311 | 312 | return 0; 313 | } 314 | ); 315 | 316 | builtin1 = WasmFunctions.wrap(store, I32, I32, I32, I32, 317 | (builtinId, opaCtxReserved, addr1) -> { 318 | String funcName = getFuncName(builtinId); 319 | 320 | checkBuiltinFunctionExists(builtinId, funcName); 321 | 322 | String arg1 = dumpJson(OPAAddr.newAddr(addr1)); 323 | 324 | switch (funcName) { 325 | case "urlquery.encode": 326 | String unquoted = arg1.substring(1, arg1.length() - 1); 327 | try { 328 | String result = arg1.charAt(0) + java.net.URLEncoder.encode(unquoted, "UTF-8") 329 | + arg1.charAt(arg1.length()-1); 330 | 331 | return loadJson(result).getInternal(); 332 | } catch(UnsupportedEncodingException e) { 333 | throw new RuntimeException(e); 334 | } 335 | default: 336 | unsupportedFunction(builtinId, funcName); 337 | } 338 | return 0; 339 | } 340 | ); 341 | 342 | builtin2 = WasmFunctions.wrap(store, I32, I32, I32, I32, I32, 343 | (builtinId, opaCtxReserved, addr1, addr2) -> { 344 | String funcName = getFuncName(builtinId); 345 | 346 | checkBuiltinFunctionExists(builtinId, funcName); 347 | 348 | unsupportedFunction(builtinId, funcName); 349 | return 0; 350 | } 351 | ); 352 | 353 | builtin3 = WasmFunctions.wrap(store, I32, I32, I32, I32, I32, I32, 354 | (builtinId, opaCtxReserved, addr1, addr2, addr3) -> { 355 | String funcName = getFuncName(builtinId); 356 | 357 | checkBuiltinFunctionExists(builtinId, funcName); 358 | 359 | unsupportedFunction(builtinId, funcName); 360 | 361 | return 0; 362 | } 363 | ); 364 | 365 | builtin4 = WasmFunctions.wrap(store, I32, I32, I32, I32, I32, I32, I32, 366 | (builtinId, opaCtxReserved, addr1, addr2, addr3, addr4) -> { 367 | String funcName = getFuncName(builtinId); 368 | 369 | checkBuiltinFunctionExists(builtinId, funcName); 370 | 371 | unsupportedFunction(builtinId, funcName); 372 | 373 | return 0; 374 | } 375 | ); 376 | 377 | Extern opaabort = Extern.fromFunc(abort); 378 | Extern opabuiltin0 = Extern.fromFunc(builtin0); 379 | Extern opabuiltin1 = Extern.fromFunc(builtin1); 380 | Extern opabuiltin2 = Extern.fromFunc(builtin2); 381 | Extern opabuiltin3 = Extern.fromFunc(builtin3); 382 | Extern opabuiltin4 = Extern.fromFunc(builtin4); 383 | Extern opaprintln = Extern.fromFunc(println); 384 | Extern opamemory = Extern.fromMemory(memory); 385 | 386 | linker.define(store, OPAConstants.MODULE, OPAConstants.OPA_ABORT, opaabort); 387 | linker.define(store, OPAConstants.MODULE, OPAConstants.OPA_BUILTIN0, opabuiltin0); 388 | linker.define(store, OPAConstants.MODULE, OPAConstants.OPA_BUILTIN1, opabuiltin1); 389 | linker.define(store, OPAConstants.MODULE, OPAConstants.OPA_BUILTIN2, opabuiltin2); 390 | linker.define(store, OPAConstants.MODULE, OPAConstants.OPA_BUILTIN3, opabuiltin3); 391 | linker.define(store, OPAConstants.MODULE, OPAConstants.OPA_BUILTIN4, opabuiltin4); 392 | linker.define(store, OPAConstants.MODULE, OPAConstants.OPA_PRINTLN, opaprintln); 393 | linker.define(store, OPAConstants.MODULE, OPAConstants.MEMORY, opamemory); 394 | } 395 | 396 | private static void checkBuiltinFunctionExists(Integer builtinId, String funcName) { 397 | if(funcName == null) { 398 | throw new UnsupportedOperationException("builtin function builtinId=" + builtinId + " not supported"); 399 | } 400 | } 401 | 402 | private static void unsupportedFunction(Integer builtinId, String funcName) { 403 | throw new UnsupportedOperationException("builtin function '" + funcName + "', builtinId=" 404 | + builtinId + " not supported"); 405 | } 406 | 407 | private void disposeImports() { 408 | if(memory != null) { 409 | memory.dispose(); 410 | memory = null; 411 | } 412 | 413 | if(builtin0 != null) { 414 | builtin0.dispose(); 415 | builtin0 = null; 416 | } 417 | 418 | if(builtin1 != null) { 419 | builtin1.dispose(); 420 | builtin1 = null; 421 | } 422 | 423 | if(builtin2 != null) { 424 | builtin2.dispose(); 425 | builtin2 = null; 426 | } 427 | 428 | if(builtin3 != null) { 429 | builtin3.dispose(); 430 | builtin3 = null; 431 | } 432 | 433 | if(builtin4 != null) { 434 | builtin4.dispose(); 435 | builtin4 = null; 436 | } 437 | 438 | if(println != null) { 439 | println.dispose(); 440 | println = null; 441 | } 442 | 443 | if(abort != null) { 444 | abort.dispose(); 445 | abort = null; 446 | } 447 | } 448 | 449 | protected void dispose(boolean disposing) { 450 | if (disposing) { 451 | if(module != null) { 452 | module.dispose(); 453 | module = null; 454 | } 455 | 456 | if(wasi != null) { 457 | wasi.dispose(); 458 | wasi = null; 459 | } 460 | 461 | if(linker != null) { 462 | linker.dispose(); 463 | linker = null; 464 | } 465 | 466 | if(store != null) { 467 | store.dispose(); 468 | store = null; 469 | } 470 | 471 | disposeImports(); 472 | } 473 | } 474 | 475 | public Integer getAbiMajorVersion() { 476 | return exports.getAbiMajorVersion(); 477 | } 478 | 479 | public Integer getAbiMinorVersion() { 480 | return exports.getAbiMinorVersion(); 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /src/test/java/UnitTest.java: -------------------------------------------------------------------------------- 1 | import static org.junit.Assert.assertEquals; 2 | import static org.junit.Assert.fail; 3 | 4 | import org.junit.Test; 5 | 6 | import io.github.sangkeon.opa.wasm.*; 7 | 8 | public class UnitTest { 9 | @Test 10 | public void bundleTest1() throws Exception { 11 | try { 12 | Bundle bundle = BundleUtil.extractBundle("./sample-policy/bundle/bundle.tar.gz"); 13 | 14 | try ( 15 | OPAModule om = new OPAModule(bundle); 16 | ) { 17 | String input = "{\"user\": \"alice\"}"; 18 | String output = om.evaluate(input, "opa/wasm/test/allowed"); 19 | 20 | assertEquals("[{\"result\":true}]", output); 21 | } 22 | 23 | } catch(Exception e) { 24 | fail(); 25 | } 26 | } 27 | 28 | @Test 29 | public void bundleTest2() throws Exception { 30 | try { 31 | Bundle bundle = BundleUtil.extractBundle("./sample-policy/bundle/bundle.tar.gz"); 32 | 33 | try ( 34 | OPAModule om = new OPAModule(bundle); 35 | ) { 36 | String input = "{\"user\": \"bob\"}"; 37 | String output = om.evaluate(input, "opa/wasm/test/allowed"); 38 | 39 | assertEquals("[{\"result\":false}]", output); 40 | } 41 | 42 | } catch(Exception e) { 43 | fail(); 44 | } 45 | } 46 | 47 | @Test 48 | public void wasmTest1() throws Exception { 49 | try ( 50 | OPAModule om = new OPAModule("./sample-policy/wasm/policy.wasm"); 51 | ) { 52 | String input = "{\"user\": \"john\"}"; 53 | String data = "{\"role\":{\"john\":\"admin\"}}"; 54 | 55 | om.setData(data); 56 | String output = om.evaluate(input); 57 | 58 | assertEquals("[{\"result\":true}]", output); 59 | } 60 | } 61 | 62 | @Test 63 | public void wasmTest2() throws Exception { 64 | try ( 65 | OPAModule om = new OPAModule("./sample-policy/wasm/policy.wasm"); 66 | ) { 67 | String input = "{\"user\": \"john\"}"; 68 | String data = "{\"role\":{\"john\":\"user\"}}"; 69 | 70 | om.setData(data); 71 | String output = om.evaluate(input); 72 | 73 | assertEquals("[{\"result\":false}]", output); 74 | } 75 | } 76 | 77 | 78 | @Test 79 | public void wasmABI1_1Test() throws Exception { 80 | try ( 81 | OPAModule om = new OPAModule("./sample-policy/wasm/policy_abi1_1.wasm"); 82 | ) { 83 | String input = "{\"user\": \"john\"}"; 84 | String data = "{\"role\":{\"john\":\"user\"}}"; 85 | 86 | om.setData(data); 87 | String output = om.evaluate(input); 88 | 89 | assertEquals(Integer.valueOf(1), om.getAbiMajorVersion()); 90 | assertEquals(Integer.valueOf(1), om.getAbiMinorVersion()); 91 | 92 | assertEquals("[{\"result\":false}]", output); 93 | } 94 | } 95 | 96 | @Test 97 | public void wasmABI1_2Test() throws Exception { 98 | try ( 99 | OPAModule om = new OPAModule("./sample-policy/wasm/policy_abi1_2.wasm"); 100 | ) { 101 | String input = "{\"user\": \"john\"}"; 102 | String data = "{\"role\":{\"john\":\"user\"}}"; 103 | 104 | om.setData(data); 105 | String output = om.evaluate(input); 106 | 107 | assertEquals(Integer.valueOf(1), om.getAbiMajorVersion()); 108 | assertEquals(Integer.valueOf(2), om.getAbiMinorVersion()); 109 | 110 | assertEquals("[{\"result\":false}]", output); 111 | } 112 | } 113 | } --------------------------------------------------------------------------------