Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dry run for removing bad APIs #1449

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -394,53 +394,6 @@ public void incTrafficMetrics(TrafficMetrics.Key key, int value) {
trafficMetrics.inc(value);
}

public static final String CONTENT_TYPE = "CONTENT-TYPE";

public boolean isRedundantEndpoint(String url, List<String> discardedUrlList){
StringJoiner joiner = new StringJoiner("|", ".*\\.(", ")(\\?.*)?");
for (String extension : discardedUrlList) {
if(extension.startsWith(CONTENT_TYPE)){
continue;
}
joiner.add(extension);
}
String regex = joiner.toString();

Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(url);
return matcher.matches();
}

private boolean isInvalidContentType(String contentType){
boolean res = false;
if(contentType == null || contentType.length() == 0) return res;

res = contentType.contains("javascript") || contentType.contains("png");
return res;
}

private boolean isBlankResponseBodyForGET(String method, String contentType, String matchContentType,
String responseBody) {
boolean res = true;
if (contentType == null || contentType.length() == 0)
return false;
res &= contentType.contains(matchContentType);
res &= "GET".equals(method.toUpperCase());

/*
* To be sure that the content type
* header matches the actual payload.
*
* We will need to add more type validation as needed.
*/
if (matchContentType.contains("html")) {
res &= responseBody.startsWith("<") && responseBody.endsWith(">");
} else {
res &= false;
}
return res;
}

public List<HttpResponseParams> filterHttpResponseParams(List<HttpResponseParams> httpResponseParamsList, AccountSettings accountSettings) {
List<HttpResponseParams> filteredResponseParams = new ArrayList<>();
int originalSize = httpResponseParamsList.size();
Expand All @@ -463,41 +416,9 @@ public List<HttpResponseParams> filterHttpResponseParams(List<HttpResponseParams

// check for garbage points here
if(accountSettings != null && accountSettings.getAllowRedundantEndpointsList() != null){
if(isRedundantEndpoint(httpResponseParam.getRequestParams().getURL(), accountSettings.getAllowRedundantEndpointsList())){
continue;
}
List<String> contentTypeList = (List<String>) httpResponseParam.getRequestParams().getHeaders().getOrDefault("content-type", new ArrayList<>());
String contentType = null;
if(!contentTypeList.isEmpty()){
contentType = contentTypeList.get(0);
}
if(isInvalidContentType(contentType)){
if (RuntimeUtil.shouldIgnore(httpResponseParam, accountSettings.getAllowRedundantEndpointsList())) {
notshivansh marked this conversation as resolved.
Show resolved Hide resolved
continue;
}

try {
List<String> responseContentTypeList = (List<String>) httpResponseParam.getHeaders().getOrDefault("content-type", new ArrayList<>());
String allContentTypes = responseContentTypeList.toString();
String method = httpResponseParam.getRequestParams().getMethod();
String responseBody = httpResponseParam.getPayload();
boolean ignore = false;
for (String extension : accountSettings.getAllowRedundantEndpointsList()) {
if(extension.startsWith(CONTENT_TYPE)){
String matchContentType = extension.split(" ")[1];
if(isBlankResponseBodyForGET(method, allContentTypes, matchContentType, responseBody)){
ignore = true;
break;
}
}
}
if(ignore){
continue;
}

} catch(Exception e){
loggerMaker.errorAndAddToDb(e, "Error while ignoring content-type redundant samples " + e.toString(), LogDb.RUNTIME);
}

}

Map<String, List<String>> reqHeaders = httpResponseParam.getRequestParams().getHeaders();
Expand Down
11 changes: 6 additions & 5 deletions apps/api-runtime/src/test/java/com/akto/parsers/TestDBSync.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.akto.dto.HttpResponseParams.Source;
import com.akto.runtime.APICatalogSync;
import com.akto.runtime.Main;
import com.akto.runtime.RuntimeUtil;
import com.akto.runtime.URLAggregator;
import com.mongodb.BasicDBObject;
import com.mongodb.client.model.Filters;
Expand Down Expand Up @@ -589,10 +590,10 @@ public void testRedundantUrlCheck() {
String url5 = "test5.js.js";

List<String> allowedUrlType = Arrays.asList("js");
assertEquals(httpCallParser.isRedundantEndpoint(url1, allowedUrlType), true);
assertEquals(httpCallParser.isRedundantEndpoint(url2, allowedUrlType), false);
assertEquals(httpCallParser.isRedundantEndpoint(url3, allowedUrlType), false);
assertEquals(httpCallParser.isRedundantEndpoint(url4, allowedUrlType), true);
assertEquals(httpCallParser.isRedundantEndpoint(url5, allowedUrlType), true);
assertEquals(RuntimeUtil.isRedundantEndpoint(url1, allowedUrlType), true);
assertEquals(RuntimeUtil.isRedundantEndpoint(url2, allowedUrlType), false);
assertEquals(RuntimeUtil.isRedundantEndpoint(url3, allowedUrlType), false);
assertEquals(RuntimeUtil.isRedundantEndpoint(url4, allowedUrlType), true);
assertEquals(RuntimeUtil.isRedundantEndpoint(url5, allowedUrlType), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,152 @@ private static CustomDataType getCustomDataTypeFromPiiType(PIISource piiSource,
return ret;
}

private void setupBadApisRemover() {
notshivansh marked this conversation as resolved.
Show resolved Hide resolved
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Integer[] accountIds = new Integer[]{1724877069, 1714700875};

for (int accountId: accountIds) {
Account account = AccountsDao.instance.findOne(eq("_id", accountId));
if (account == null) {
continue;
}

try {
Context.accountId.set(accountId);
List<ApiCollection> apiCollections = ApiCollectionsDao.instance.getMetaAll();
Map<Integer, ApiCollection> apiCollectionMap = apiCollections.stream().collect(Collectors.toMap(ApiCollection::getId, Function.identity()));

List<SampleData> sampleDataList = new ArrayList<>();
Bson filters = Filters.empty();
int skip = 0;
int limit = 100;
Bson sort = Sorts.ascending("_id.apiCollectionId", "_id.url", "_id.method");
AccountSettings accountSettings = AccountSettingsDao.instance.findOne(AccountSettingsDao.generateFilter());

do {
sampleDataList = SampleDataDao.instance.findAll(filters, skip, limit, sort);
skip += limit;
List<Key> toBeDeleted = new ArrayList<>();
for(SampleData sampleData: sampleDataList) {
try {
List<String> samples = sampleData.getSamples();
if (samples == null || samples.isEmpty()) {
logger.info("[BadApisRemover] No samples found for : " + sampleData.getId());
continue;
}

ApiCollection apiCollection = apiCollectionMap.get(sampleData.getId().getApiCollectionId());
if (apiCollection == null) {
logger.info("[BadApisRemover] No apiCollection found for : " + sampleData.getId());
continue;
}


boolean allMatchDefault = true;

for (String sample : samples) {
HttpResponseParams httpResponseParams = HttpCallParser.parseKafkaMessage(sample);
if(accountSettings != null && accountSettings.getAllowRedundantEndpointsList() != null){
if (!RuntimeUtil.shouldIgnore(httpResponseParams, accountSettings.getAllowRedundantEndpointsList())) {
allMatchDefault = false;
break;
}
}
}

if (allMatchDefault) {
logger.info("[BadApisRemover] Deleting bad API: " + toBeDeleted, LogDb.DASHBOARD);
toBeDeleted.add(sampleData.getId());
}
} catch (Exception e) {
loggerMaker.errorAndAddToDb("[BadApisRemover] Couldn't delete an api for default payload: " + sampleData.getId() + e.getMessage(), LogDb.DASHBOARD);
}
}

// deleteApis(toBeDeleted);
notshivansh marked this conversation as resolved.
Show resolved Hide resolved

} while (!sampleDataList.isEmpty());

} catch (Exception e) {
loggerMaker.errorAndAddToDb("Couldn't complete scan for APIs remover: " + e.getMessage(), LogDb.DASHBOARD);
}

}

AccountTask.instance.executeTask(new Consumer<Account>() {
notshivansh marked this conversation as resolved.
Show resolved Hide resolved
@Override
public void accept(Account account) {
AccountSettings accountSettings = AccountSettingsDao.instance.findOne(AccountSettingsDao.generateFilter());
Map<String, DefaultPayload> defaultPayloadMap = accountSettings.getDefaultPayloads();
if (defaultPayloadMap == null || defaultPayloadMap.isEmpty()) {
return;
}

List<ApiCollection> apiCollections = ApiCollectionsDao.instance.getMetaAll();
Map<Integer, ApiCollection> apiCollectionMap = apiCollections.stream().collect(Collectors.toMap(ApiCollection::getId, Function.identity()));

for(Map.Entry<String, DefaultPayload> entry: defaultPayloadMap.entrySet()) {
DefaultPayload dp = entry.getValue();
String base64Url = entry.getKey();
if (!dp.getScannedExistingData()) {
try {
List<SampleData> sampleDataList = new ArrayList<>();
Bson filters = Filters.empty();
int skip = 0;
int limit = 100;
Bson sort = Sorts.ascending("_id.apiCollectionId", "_id.url", "_id.method");
do {
sampleDataList = SampleDataDao.instance.findAll(filters, skip, limit, sort);
skip += limit;
List<Key> toBeDeleted = new ArrayList<>();
for(SampleData sampleData: sampleDataList) {
try {
List<String> samples = sampleData.getSamples();
ApiCollection apiCollection = apiCollectionMap.get(sampleData.getId().getApiCollectionId());
if (apiCollection == null) continue;
if (apiCollection.getHostName() != null && !dp.getId().equalsIgnoreCase(apiCollection.getHostName())) continue;
if (samples == null || samples.isEmpty()) continue;

boolean allMatchDefault = true;

for (String sample : samples) {
HttpResponseParams httpResponseParams = HttpCallParser.parseKafkaMessage(sample);
if (!matchesDefaultPayload(httpResponseParams, defaultPayloadMap)) {
allMatchDefault = false;
break;
}
}

if (allMatchDefault) {
loggerMaker.errorAndAddToDb("Deleting API that matches default payload: " + toBeDeleted, LogDb.DASHBOARD);
toBeDeleted.add(sampleData.getId());
}
} catch (Exception e) {
loggerMaker.errorAndAddToDb("Couldn't delete an api for default payload: " + sampleData.getId() + e.getMessage(), LogDb.DASHBOARD);
}
}

deleteApis(toBeDeleted);

} while (!sampleDataList.isEmpty());

Bson completedScan = Updates.set(AccountSettings.DEFAULT_PAYLOADS+"."+base64Url+"."+DefaultPayload.SCANNED_EXISTING_DATA, true);
AccountSettingsDao.instance.updateOne(AccountSettingsDao.generateFilter(), completedScan);

} catch (Exception e) {

loggerMaker.errorAndAddToDb("Couldn't complete scan for default payload: " + e.getMessage(), LogDb.DASHBOARD);
}
}
}
}
}, "setUpDefaultPayloadRemover");
}
}, 0, 5, TimeUnit.MINUTES);
}

private void setUpDefaultPayloadRemover() {
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
Expand Down Expand Up @@ -700,15 +846,34 @@ private <T> void deleteApisPerDao(List<Key> toBeDeleted, AccountsContextDao<T> d
if (toBeDeleted == null || toBeDeleted.isEmpty()) return;
List<WriteModel<T>> stiList = new ArrayList<>();

int counter = 0;
for(Key key: toBeDeleted) {
stiList.add(new DeleteManyModel<>(Filters.and(
Filters.eq(prefix + "apiCollectionId", key.getApiCollectionId()),
Filters.eq(prefix + "method", key.getMethod()),
Filters.eq(prefix + "url", key.getUrl())
)));
counter ++;

if (counter%20 == 0) {
try {
dao.bulkWrite(stiList, new BulkWriteOptions().ordered(false));
} catch (Exception e) {
loggerMaker.errorAndAddToDb(e, "Error while deleting apis: " + e.getMessage(), LogDb.DASHBOARD);
}

stiList = new ArrayList<>();
}
}

if (!stiList.isEmpty()) {
try {
dao.bulkWrite(stiList, new BulkWriteOptions().ordered(false));
} catch (Exception e) {
loggerMaker.errorAndAddToDb(e, "Error while deleting apis: " + e.getMessage(), LogDb.DASHBOARD);
}
}

dao.bulkWrite(stiList, new BulkWriteOptions().ordered(false));
}

private void deleteApis(List<Key> toBeDeleted) {
Expand Down
Loading
Loading